js引擎内部实现类型转换的4个抽象操作
隐式类型转换是在一定场景下,js运行环境自动调用这几个方法,尝试转换成期望的数据类型
- ToString(argument)
- ToNumber(argument)
- ToBoolean(argument)
- ToPrimitive(input[ , PreferredType])
ToString
这里所说的
ToString
可不是对象的toString方法
,而是指其他类型的值转换为字符串类型的操作。
输入类型 | 结果 |
---|---|
Undefined | "undefined" |
Null | "null" |
Boolean | 如果参数是 true,那么结果为 "true"。 如果参数是 false,那么结果为 "false"。 |
String | 结果等于输入的参数(不转换)。 |
Number | NaN => "NaN" +0, -0 => "0" -10 => "-10" Infinity => "Infinity" 极小或者极大的数值使用指数形式 |
Object | 应用下列步骤:
|
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(10) // '10'
String(1e21) // '1e+21'
String([1,2,3]) // '1,2,3'
String([]) // ''
String([null]) // ''
String([1, undefined, 3]) // '1,,3'
String({}) // '[object Objecr]'
对象的toString
方法,满足ToString
操作的规则。
注意:上面所说的规则是在默认的情况下,如果修改默认的
toString()
方法,会导致不同的结果
ToNumber
ToNumber
指其他类型转换为数字类型的操作。
输入类型 | 结果 |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | 如果参数是 true,结果为 1。如果参数是 false,此结果为 +0。 |
Number | 结果等于输入的参数(不转换)。 |
String | 如果是纯数字形式,则转为对应的数字,空字符转为0 , 否则一律按转换失败处理,转为NaN |
Object | 应用下列步骤:
|
Number(null) // 0
Number(undefined) // NaN
Number('10') // 10
Number('10a') // NaN
Number('') // 0
Number(true) // 1
Number(false) // 0
Number([]) // 0
Number(['1']) // 1
Number({}) // NaN
ToBoolean
ToBoolean
指其他类型转换为布尔类型的操作。
输入类型 | 结果 |
---|---|
Undefined | false |
Null | false |
Boolean | 结果等于输入的参数(不转换)。 |
Number | 如果参数是 +0, -0, 或 NaN,结果为 false ;否则结果为 true。 |
String | 如果参数参数是空字符串(其长度为零),结果为 false,否则结果为 true。 |
Object | true |
Boolean(null) // false
Boolean(undefined) // false
Boolean('') // flase
Boolean(NaN) // flase
Boolean(0) // flase
Boolean([]) // true
Boolean({}) // true
Boolean(Infinity) // true
总结:js中的假值只有false
、null
、undefined
、空字符
、0
和NaN
,其它值转为布尔型都为true
。
ToPrimitive
ToPrimitive
指对象类型类型(如:对象、数组)转换为原始类型的操作。ToPrimitive(input[ , PreferredType])
input是要转换的值,PreferredType是可选参数,可以是Number或String类型。它只是一个转换标志,转化后的结果并不一定是这个参数所值的类型,但是转换结果一定是一个原始值(或者报错)。
PreferredType为Number
转换流程:
- 如果输入的值已经是一个原始值,则直接返回它;
- 否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,如果valueOf()方法的返回值是一个原始值,则返回这个原始值;
- 否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值;
- 否则,抛出TypeError异常。
PreferredType为String
转换流程:
- 如果输入的值已经是一个原始值,则直接返回它;
- 否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值;
- 否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,如果valueOf()方法的返回值是一个原始值,则返回这个原始值;
- 否则,抛出TypeError异常。
PreferredType参数不存在
PreferredType的值会按照这样的规则来自动设置:
- 该对象为Date类型,则PreferredType被设置为String;
- 否则,PreferredType被设置为Number;
// 对于日期
var date = new Date();
console.log(date == date.toString()); // true
var obj = {
valueOf() {
return 20
},
toString() {
return 30
}
}
console.log(obj + 2); // 22
var obj1 = {
valueOf() {
return 20
},
toString() {
return []
}
}
console.log(obj1 + 2); // 22
var obj2 = {
valueOf() {
return []
},
toString() {
return 30
}
}
console.log(obj2 + 2); // 32
var obj3 = {
valueOf() {
return {}
},
toString() {
return []
}
}
console.log(obj3 + 2); // Uncaught TypeError: Cannot convert object to primitive value
宽松相等(==)比较时的隐式转换规则
ES5文档 == 运算规则的描述如下
比较运算`x`==`y`, 其中`x`和` y`是值,产生true或者false。这样的比较按如下方式进行:
1. 若Type(x)与Type(y)相同, 则
a. 若Type(x)为Undefined, 返回true。
b. 若Type(x)为Null, 返回true。
c. 若Type(x)为Number, 则
①. 若x为NaN, 返回false。
②. 若y为NaN, 返回false。
③. 若x与y为相等数值, 返回true。
④. 若x 为 +0 且 y为−0, 返回true。
⑤. 若x 为 −0 且 y为+0, 返回true。
⑥. 返回false。
d. 若Type(x)为String, 则当x和y为完全相同的字符序列(长度相等且相同字符在相同位置)时返回true。 否则, 返回false。
e. 若Type(x)为Boolean, 当x和y为同为true或者同为false时返回true。 否则, 返回false。
f. 当x和y为引用同一对象时返回true。否则,返回false。
2. 若x为null且y为undefined, 返回true。
3. 若x为undefined且y为null, 返回true。
4. 若Type(x) 为 Number 且 Type(y)为String, 返回comparison x == ToNumber(y)的结果。
5. 若Type(x) 为 String 且 Type(y)为Number,
6. 返回比较ToNumber(x) == y的结果。
7. 若Type(x)为Boolean, 返回比较ToNumber(x) == y的结果。
8. 若Type(y)为Boolean, 返回比较x == ToNumber(y)的结果。
9. 若Type(x)为String或Number,且Type(y)为Object,返回比较x == ToPrimitive(y)的结果。
10. 若Type(x)为Object且Type(y)为String或Number, 返回比较ToPrimitive(x) == y的结果。
11. 返回false
1. 布尔类型和其他类型的相等比较
- 只要
布尔类型
参与比较,该布尔类型
的值首先会被转换为数字类型
- 根据
布尔类型
的ToNumber
规则,true
转为1
,false
转为0
false == 0 // true
true == 1 // true
true == 2 // false
2. 数字类型和字符串类型的相等比较
- 当
数字类型
和字符串类型
做相等比较时,字符串类型
会被转换为数字类型
- 根据字符串的
ToNumber
规则,如果是纯数字形式的字符串,则转为对应的数字,空字符转为0
, 否则一律按转换失败处理,转为NaN
0 == '' // true
1 == '1' // true
1e21 == '1e21' // true
Infinity == 'Infinity' // true
true == '1' // true
false == '0' // true
false == '' // true
3. 对象类型和原始类型的相等比较
- 当
对象类型
和原始类型
做相等比较时,对象类型
会依照ToPrimitive
规则转换为原始类型
'[object Object]' == {} // true
'1,2,3' == [1, 2, 3] // true
[2] == 2 // true
数组[2]是对象类型,所以会进行ToPrimitive操作,也就是先调用valueOf再调用toString,根据数组ToString操作规则,会得到结果"2",而字符串"2"再和数字2比较时,会先转为数字类型,所以最后得到的结果为true
[null] == 0 // true
[undefined] == 0 // true
[] == 0 // true
根据上文中提到的数组ToString
操作规则,数组元素为null
或undefined
时,该元素被当做空字符串
处理,而空数组[]
也被转为空字符串
,所以上述代码相当于
'' == 0 // true
'' == 0 // true
'' == 0 // true
空字符串
会转换为数字0
,所以结果为true
。
const a = {
valueOf () {
return 10
},
toString () {
return 20
}
}
a == 10 // true
对象的ToPrimitive
操作会先调用valueOf
方法,并且a
的valueOf
方法返回一个原始类型的值,所以ToPrimitive
的操作结果就是valueOf
方法的返回值10
。
4. null、undefined和其他类型的比较
首先得知道null
与undefined
的定义
null
: 代表“空值”,代表一个空对象指针,使用typeof
运算得到 "object"
,所以可以认为它是一个特殊的对象值。
undefined
:声明了一个变量但并未为该变量赋值,此变量的值默认为undefined。
ECMAScript规范
中规定null
和undefined
之间互相宽松相等(==)
,并且也与其自身相等,但和其他所有的值都不宽松相等(==)
null
和undefined
宽松相等的结果为true,这一点大家都知道- 其次,
null
和undefined
都是假值
null == false // false
undefined == false // false
5. 对象和对象相比较
引用类型
:是指存放在堆内存
中的对象,变量名保存在栈内存
里,而对应的值保存在堆内存
里,这个变量在栈内存
中实际保存的是:这个值在堆内存中的地址
,也就是指针
,指针指向堆内存中的地址。
比较的规则
- 如果两个对象指向同一个对象,相等操作符返回
true
,否则为false
var a = {};
var b = {};
a == b // false
var c = [];
var d = c;
c == d // true
[] == ![] // true
{} == !{} // false
JS规定,逻辑非 (!)
的优先级高于相等操作符 ( == )
![]转换成boolean为false, 即比较 [] == false, 这下变成了对象类型
与布尔类型
相比较了, 很容易的出比较变为'' == 0,
再根据字符串与数字类型相比较规则,比较变为0 == 0
,显然结果为true;
!{}转换成boolean为false, 即比较 {} == false,{}的valueOf()方法得到的不是原始类型,继续执行{}的toString()方法到的结果为"[object object]"
,比较变为"[object object]" == 0
,再根据字符串与数字类型相比较规则,"[object object]"
转为数字类型为NaN
,比较变成NaN == 0
,NaN
不与任何值相等,显然结果为false
一道面试题
定义一个变量a
,使得下面的表达式结果为true
a == 1 && a == 2 && a == 3 // true
利用valueOf
const a = {
// 定义一个属性来做累加
i: 1,
valueOf () {
return this.i++
}
}
a == 1 && a == 2 && a == 3 // true
或者利用toString
const a = {
// 定义一个属性来做累加
i: 1,
toString () {
return this.i++
}
}
a == 1 && a == 2 && a == 3 // true
利用Symbol.toPrimitive
const a = {
// 定义一个属性来做累加
i: 1,
[Symbol.toPrimitive] () {
return this.i++
}
}
a == 1 && a == 2 && a == 3 // true
参考资料: