let
,const
和用var
来声明变量的区别
用var
- 不区分变量和常量
- 允许重新声明
- 存在变量提升
- 没有块级作用域
- 是顶层对象的属性
let
,const
- 暂时性死区
- 不允许重复声明
- 块级作用域
- 不是顶层对象的属性
const
变量指向的那个内存地址所保存的数据不得改动
介绍 js 的数据类型
JS 中分为七种内置类型,七种内置类型又分为两大类型:基本类型和引用类型。
基本类型有六种: null
,undefined
,boolean
,number
,string
,symbol
。
javascript 中除了上面的基本类型之外就是引用类型(对象、数组和函数)。
基本数据类型和引⽤类型的区别
原始数据类型直接存储在栈(stack)中,占据空间小、大小固定(不可变);
引用数据类型存储在堆(heap)中,占据空间大、大小不固定(可变);引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
它们不同的表现主要体现在判断变量是否相等和变量值引用两方面,比如:
//判断变量是否相等
//基本数据类型
const a = 'hello';
const b = 'hello';
a === b; //true
//引用数据类型
const c = { a: 'hello' };
const d = { a: 'hello' };
c === d; //false
2
3
4
5
6
7
8
9
10
11
//变量值引用
//引用数据类型
let a = { a: { b: 'hello' } };
let b = a;
b.a = 'hi';
a.a; //'hi'
2
3
4
5
6
7
深浅拷贝的方法
- 浅拷贝
Object.assign
,运算符 ...
- 深拷贝
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();
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;
}
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)
基本数据类型既然不是对象,为什么它会有属性和方法呢?,比如 除了 null
和 undefined
之外,所有基本类型都有其对应的包装对象,例如:
只要引用了字符串 's' 的属性,JavaScript 就会将字符串值通过调用 new String('s')
的方式转换成对象,这个对象继承了字符串的方法并被用来处理属性的引用。一旦属性引用结束,这个新创建的对象就会销毁(其实在实现上并不一定创建或销毁这个临时对象,然而整个过程看起来是这样)。null
和 undefined
没有包装对象:访问它们的属性会造成一个类型错误。
在读取字符串、数字和布尔值的属性值(或方法)的时候,表现的像对象一样。但如果你试图给其属性赋值,则会表现得不如你所愿:修改只是发生在临时对象身上,而这个临时对象并未继续保留下来。
const s = 'test'; //创建一个字符串
s.len = 4; //给它设置一个属性
const t = s.len; //查询这个属性undefined
2
3
null
和 undefined
的区别
undefined
是声明了但未赋值,null
是声明了并且赋值为空值
如何判断变量的类型
typeof
对于基本数据类型来说,typeof
除了 null
都可以显示正确的类型
typeof 1; // 'number'
typeof '1'; // 'string'
typeof undefined; // 'undefined'
typeof true; // 'boolean'
typeof Symbol(); // 'symbol'
typeof b; // undefined
2
3
4
5
6
对于引用类型来说,typeof
除了函数都会显示 object
typeof []; // 'object'
typeof {}; // 'object'
typeof console.log; // 'function'
2
3
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
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
2
3
4
5
6
这其实也侧面反映了一个问题, instanceof 也不是百分之百可信的。
Object.prototype.toString.call(xx).slice(8, -1)
对于任何变量都可以通过 Object.prototype.toString.call(xx)
获得一个正确的类型,通过它我们可以获得一个类似 [object Type] 的字符串。
Object.prototype.toString.call('').slice(8, -1); //"String"
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__
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
== 的比较规则
- 首先会判断两者类型是否相同。相同的话就是比大小了
- 类型不相同的话,那么就会进行类型转换
- 会先判断是否在对比
null
和undefined
,是的话就会返回true
- 判断两者类型是否为
string
和number
,是的话就会将字符串转换为number
1 == '1'
↓
1 == 1
2
3
- 判断其中一方是否为
boolean
,是的话就会把boolean
转为number
再进行判断
'1' == true
↓
'1' == 1
↓
1 == 1
2
3
4
5
- 判断其中一方是否为
object
且另一方为string
、number
或者symbol
,是的话就会把object
转为原始类型再进行判断
'1' == { name: 'yck' }
↓
'1' == '[object Object]'
2
3
详细完整流程如下:
==
和 ===
区别,什么时候用 ==
或 ===
区别:前者会进行类型转换,后者不会。
使用:除了下面情况其他全部用 ===
if (a == null) {
// 这里相当于 a === null || a === undefined, 简写形式
// 这是 jQuery 源码中推荐的写法
}
2
3
4
类型转换
在 JS 中类型转换只有三种情况,分别是:
- 转换为布尔值
- 转换为数字
- 转换为字符串
- 转
Boolean
在条件判断时,除了 undefined
, null
, false
, NaN
, ''
, 0
, -0
,其他所有值都转为 true
,包括所有对象。
- 对象转原始类型
对象在转换类型的时候,会调用内置的[[ToPrimitive]]
函数,对于该函数来说,算法逻辑一般来说如下:
- 如果已经是原始类型了,那就不需要转换了
- 调用
x.valueOf()
,如果转换为基础类型,就返回转换的值 - 调用
x.toString()
,如果转换为基础类型,就返回转换的值 - 如果都没有返回原始类型,就会报错
- 可以重写
Symbol.toPrimitive
,该方法在转原始类型时调用优先级最高。
let a = {
valueOf() {
return 0;
},
toString() {
return '1';
},
[Symbol.toPrimitive]() {
return 2;
}
};
1 + a; // => 3
2
3
4
5
6
7
8
9
10
11
12