letconst和用var来声明变量的区别

  1. var
  • 不区分变量和常量
  • 允许重新声明
  • 存在变量提升
  • 没有块级作用域
  • 是顶层对象的属性
  1. letconst
  • 暂时性死区
  • 不允许重复声明
  • 块级作用域
  • 不是顶层对象的属性
  • const变量指向的那个内存地址所保存的数据不得改动

介绍 js 的数据类型

JS 中分为七种内置类型,七种内置类型又分为两大类型:基本类型和引用类型。

基本类型有六种: nullundefinedbooleannumberstringsymbol

javascript 中除了上面的基本类型之外就是引用类型(对象、数组和函数)。

基本数据类型和引⽤类型的区别

原始数据类型直接存储在栈(stack)中,占据空间小、大小固定(不可变);

引用数据类型存储在堆(heap)中,占据空间大、大小不固定(可变);引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

它们不同的表现主要体现在判断变量是否相等和变量值引用两方面,比如:

//判断变量是否相等

//基本数据类型
const a = 'hello';
const b = 'hello';
a === b; //true

//引用数据类型
const c = { a: 'hello' };
const d = { a: 'hello' };
c === d; //false
1
2
3
4
5
6
7
8
9
10
11
//变量值引用

//引用数据类型
let a = { a: { b: 'hello' } };
let b = a;
b.a = 'hi';
a.a; //'hi'
1
2
3
4
5
6
7

深浅拷贝的方法

  1. 浅拷贝

Object.assign,运算符 ...

  1. 深拷贝

JSON.parse(JSON.stringify(object))

但是该方法也是有局限性的:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象

如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel

function structuralClone(obj) {
  return new Promise(resolve => {
    const { port1, port2 } = new MessageChannel();
    port2.onmessage = ev => resolve(ev.data);
    port1.postMessage(obj);
  });
}

var obj = {
  a: 1,
  b: {
    c: 2
  }
};

obj.b.d = obj.b;

// 注意该方法是异步的
// 可以处理 undefined 和循环引用对象
const test = async () => {
  const clone = await structuralClone(obj);
  console.log(clone);
};
test();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

最后一个简单的深拷贝函数

function deepClone(obj) {
  function isObject(o) {
    return (
      (typeof o === 'object' || typeof o === 'function') &&
      o !== null
    );
  }

  if (!isObject(obj)) {
    throw new Error('非对象');
  }

  let isArray = Array.isArray(obj);
  let newObj = isArray ? [...obj] : { ...obj };
  Reflect.ownKeys(newObj).forEach(key => {
    newObj[key] = isObject(obj[key])
      ? deepClone(obj[key])
      : obj[key];
  });

  return newObj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

基本数据类型既然不是对象,为什么它会有属性和方法呢?,比如 'hello'.length,'hi'.slice(0, 1)

除了 nullundefined之外,所有基本类型都有其对应的包装对象,例如:

只要引用了字符串 's' 的属性,JavaScript 就会将字符串值通过调用 new String('s')的方式转换成对象,这个对象继承了字符串的方法并被用来处理属性的引用。一旦属性引用结束,这个新创建的对象就会销毁(其实在实现上并不一定创建或销毁这个临时对象,然而整个过程看起来是这样)。nullundefined 没有包装对象:访问它们的属性会造成一个类型错误。

在读取字符串、数字和布尔值的属性值(或方法)的时候,表现的像对象一样。但如果你试图给其属性赋值,则会表现得不如你所愿:修改只是发生在临时对象身上,而这个临时对象并未继续保留下来。

const s = 'test'; //创建一个字符串
s.len = 4; //给它设置一个属性
const t = s.len; //查询这个属性undefined
1
2
3

nullundefined 的区别

undefined是声明了但未赋值,null是声明了并且赋值为空值

如何判断变量的类型

  1. typeof

对于基本数据类型来说,typeof 除了 null 都可以显示正确的类型

typeof 1; // 'number'
typeof '1'; // 'string'
typeof undefined; // 'undefined'
typeof true; // 'boolean'
typeof Symbol(); // 'symbol'
typeof b; //  undefined
1
2
3
4
5
6

对于引用类型来说,typeof 除了函数都会显示 object

typeof []; // 'object'
typeof {}; // 'object'
typeof console.log; // 'function'
1
2
3
  1. instanceof

对于引用类型来说,可以考虑使用instanceof,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype

const Person = function() {};
const p1 = new Person();
p1 instanceof Person; // true

var str = 'hello world';
str instanceof String; // false

var str1 = new String('hello world');
str1 instanceof String; // true
1
2
3
4
5
6
7
8
9

对于基本类型来说,你想直接通过 instanceof 来判断类型是不行的,当然我们还是有办法让 instanceof 判断原始类型的

class PrimitiveString {
  static [Symbol.hasInstance](x) {
    return typeof x === 'string';
  }
}
console.log('hello world' instanceof PrimitiveString); // true
1
2
3
4
5
6

这其实也侧面反映了一个问题, instanceof 也不是百分之百可信的。

  1. Object.prototype.toString.call(xx).slice(8, -1)

对于任何变量都可以通过 Object.prototype.toString.call(xx)获得一个正确的类型,通过它我们可以获得一个类似 [object Type] 的字符串。

Object.prototype.toString.call('').slice(8, -1); //"String"
1

模拟实现instanceof

function instanceof(left, right) {
    // 获得类型的原型
    let right = right.prototype
    // 获得对象的原型
    left = left.__proto__
    // 判断对象的类型是否等于类型的原型
    while (true) {
    	if (left === null)
    		return false
    	if (right === left)
    		return true
    	left = left.__proto__
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

== 的比较规则

  1. 首先会判断两者类型是否相同。相同的话就是比大小了
  2. 类型不相同的话,那么就会进行类型转换
  3. 会先判断是否在对比 nullundefined,是的话就会返回 true
  4. 判断两者类型是否为 stringnumber,是的话就会将字符串转换为 number
1 == '1'1 ==  1
1
2
3
  1. 判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
'1' == true'1' ==  11  ==  1
1
2
3
4
5
  1. 判断其中一方是否为 object 且另一方为 stringnumber 或者 symbol,是的话就会把 object 转为原始类型再进行判断
'1' == { name: 'yck' }'1' == '[object Object]'
1
2
3

详细完整流程如下: == 的比较规则

=====区别,什么时候用 =====

区别:前者会进行类型转换,后者不会。

使用:除了下面情况其他全部用 ===

if (a == null) {
  // 这里相当于 a === null || a === undefined, 简写形式
  // 这是 jQuery 源码中推荐的写法
}
1
2
3
4

类型转换

在 JS 中类型转换只有三种情况,分别是:

  • 转换为布尔值
  • 转换为数字
  • 转换为字符串
  1. Boolean

在条件判断时,除了 undefinednullfalseNaN''0-0,其他所有值都转为 true,包括所有对象。

  1. 对象转原始类型

对象在转换类型的时候,会调用内置的[[ToPrimitive]]函数,对于该函数来说,算法逻辑一般来说如下:

  • 如果已经是原始类型了,那就不需要转换了
  • 调用 x.valueOf(),如果转换为基础类型,就返回转换的值
  • 调用 x.toString(),如果转换为基础类型,就返回转换的值
  • 如果都没有返回原始类型,就会报错
  • 可以重写 Symbol.toPrimitive ,该方法在转原始类型时调用优先级最高。
let a = {
  valueOf() {
    return 0;
  },
  toString() {
    return '1';
  },
  [Symbol.toPrimitive]() {
    return 2;
  }
};
1 + a; // => 3
1
2
3
4
5
6
7
8
9
10
11
12

TOC