JS中目前共有7种数据类型:Undefined、Null、Boolean、Number、String、Symbol 和 Object。
前 6 者是基本类型数据(直接栈内保存),Object 是引用类型数据(在堆中生成,在栈内保存的是该对象的引用)。
JS 是一门弱类型语言。它不需要事先具体声明变量的类型,在程序运行过程中,类型会被自动推断确定。因此,可以用同一个变量保存不同类型的数据:
var a = 1;
a = 'abc';
a = {
x: 1
};
装箱
除此之外,还有一点不同就是:基本类型数据本身不可变,但引用类型数据是可变的。
引用类型数据是可变的,不言而喻,是指我们可以修改其属性值。
var a = {
x: 1
};
a.x = 2;
console.log(a); // => {x: 2}
console.log(a.x); // 2
然而在 JS 中基本类型变量也是可以使用“点”的,比如:
var a = 1;
a.x = 2;
console.log(a);// 1
console.log(a.x);// undefined
上述代码运行过程中发生了所谓的“装箱”操作。
a.x = 2
// 等价于
var temp = new Number(a)
temp.x = 2
temp = null
因为 2 是基本类型,在取其属性时,先用对应的 Number 构造函数包裹成一个临时对象,然后再对临时对象取属性值操作,随后这个临时对象便销毁。
整个过程称为“装箱”。知道这个过程后,那么整段代码就好理解了:
var a = 1;
var temp1 = new Number(a);
temp1.x = 2;
temp1 = null;
console.log(a);// 1
var temp2 = new Number(a);
console.log(temp2.x);// undefined temp2 = null;
两次 a.x,有两次“装箱”,因此是两个不同的临时对象。给一个对象存值,然后再去另外一个对象那里取值,必然是取不到的。
参考JS 文档规范,以“[]”属性访问方式为例
这里再具体举一个例子,来看看整个流程。比如,a = 1,然后获取 a[‘x’] 。
- 计算表达式 a。(这里用表达式是考虑到像这种情形:a.b[‘x’],此时表达式是 a.b)
- 获取上一步结果的值,这里是1。
- 计算表达式 ‘x’。(这里用表达式是考虑到像这种情形:a[‘x’+’y’],此时表达式 ‘x’+’y’)
- 获取第 3 步的结果,即 ‘x’。
- 把第 2 步的结果传入 ToObject,即 ToObject(1)。这里记做 temp
- 把第 4 步,转化为字符串,当然还是 ‘x’
- 返回,temp[‘x’]
其中第 5 步,最为关键。它用 ToObject 生成个临时对象(因为它只是局部变量),我们最后取到的属性值正是这个对象的属性值。
ToObject 的具体操作为
| 输入 | 输出 |
|---|---|
| undefined | 报错 |
| null | 报错 |
| boolean | Create a new Boolean object |
| number | Create a new Number object |
| string | Create a new String object |
| object | 返回输入内容本身 |
拆箱
对引用类型进行那些基本类型“才该有的”操作,即“拆箱操作”。
var a = 1;
var b = {};
console.log(a - b);
对普通对象进行减法操作时,对象需要转化为数字类型。这时调用了内部操作 ToNumber
ToNumber 的具体操作为
| 输入 | 输出 |
|---|---|
| undefined | NaN |
| null | +0 |
| boolean | true => 1, false => +0 |
| number | 返回输入内容本身 |
| string | See grammar and note below |
| object | 1. ToPrimitive 2. ToNumber |
由上文可知,对object进行ToNumber操作时,会经历两步,1. ToPrimitive 2. ToNumber
ToPrimitive(转为基本类型)的具体操作为
| 输入 | 输出 |
|---|---|
| undefined | 返回输入内容本身 |
| null | 返回输入内容本身 |
| boolean | 返回输入内容本身 |
| number | 返回输入内容本身 |
| string | 返回输入内容本身 |
| object | 获取对象的默认值。使用内部的[[DefaultValue]](hint) |
对于 [[DefaultValue]](hint), 根据 hint 值采取不同的处理方式
比如 hint 是 String 时,优先调用对象的 toString 方法,如果返回值是基本类型值,返回该值,否则调用对象的 valueOf 方法,如果返回值是基本类型值,返回该值。否则报错。
而 hint 是 Number 时,顺序是反过来的,优先调用 valueOf,如果其返回值不是基本类型,再调用 toString。
另外,除了日期对象外,如果没传 hint 的话,其默认值是 Number,因此 JS 中类型转化时,更偏爱 Number。
下面我们举几个例子看看:
var a = {
toString() {
return 3
},
valueOf() {
return '30'
}
};
console.log(a - 5); // 25
这里使用的是减法操作,此时 hint 是 Number,因此先调用对象 a 的 valueOf 方法,其返回值 ‘30’ 是字符串类型,是基本类型。因此 a - 5 变成了 ‘30’ - 5。
再看:
var a = {
toString() {
return {}
},
valueOf: null
};
console.log(a - 5); // Uncaught TypeError: Cannot convert object to primitive value
对象 a,其方法 valueOf 不是函数,因而看其 toString 方法,而该方法返回的是一个空对象,不是基本类型。因而报错。
林秀栋的技术博客