基本概念
Primitive 类型
即基本类型,除开对象外的六种类型
- null
- undefined
- boolean
- number
- string
- symbol 在js运算符号中,大多都是将对象转为基本类型,再进行计算
ToPrimitive
一个js内部的方法,作用是将对象转换为基本类型,接受一个输入值input 和一个可选的hint,hint支持两个值,'string','number',大致转换规则如下:
-
如果input 为基本类型,不转换直接返回
-
如果hint为'number'
- 1.调用input的valueOf方法,如果返回的是基本类型,则直接返回,若不是则继续往下
- 2.调用input的toString()方法,如果返回的是基本类型,则直接返回,若不是继续往下
- 3.以上两步没得到基本类型,则抱typeerror错误
-
如果hint为'string'
- 1.调用input的toString()方法,如果返回的是基本类型,则直接返回,若不是继续往下
- 2.调用input的valueOf方法,如果返回的是基本类型,则直接返回,若不是则继续往下
- 3.以上两步没得到基本类型,则抱typeerror错误
-
如果hint为空
- 1.如果input为Date类型,则按hint='string'处理
- 2.否则按照hint='number'处理
toNumber
js内部的方法,作用是将对象转换为数值类型,接受一个输入值input,input类型与返回结果如下表
- Undefined 结果为 NaN
- Null 结果为0
- Boolean 如果参数是 true,结果为 1。如果参数是 false,此结果为 +0
- Number 结果等于输入的参数(不转换)
- String 这里es5的规范些了一堆,总结下就是
- Object
- 1.按照hint为'number'调用ToPrimitive,得到result。
- 2.返回 ToNumber(result)。
运算符
加法运算符
在js里面加法运算符比较特殊,既能代表数字相加操作,也能代表字符串拼接,具体机制总结一下
- 先将左右两边操作数调用ToPrimitive
- 如果两个任何一个为string类型,则执行字符串拼接操作
- 返回两个数toNumber后相加的值
到这里我们已经可以解决
[]+{}
这个问题了,步骤拆解如下 - 对[]调用ToPrimitive
- 1.
[].valueOf()
返回自身 - 2.
[].toString()
返回''
- 3.返回
''
- 1.
- 对{}调用ToPrimitive
- 1.
[].valueOf()
返回自身 - 2.
[].toString()
返回'[object Object]'
- 3.返回
'[object Object]'
- 1.
- 因为两边都是字符串,所以执行字符串拼接操作最后结果为'[object Object]' 还有诸多类似的情况
console.log([]+[]) // ''
console.log(1+{}) // '1[object Object]'
console.log([]+1) // '1'
consoke.log(undefined + 1) // NaN
const a = {}
const a = {
valueOf () {
return {}
},
toString () {
return 'obj a'
},
}
const b = {
valueOf () {
return 1;
},
toString () {
return 'obj b';
}
}
console.log(a + b) //'obj a1'
减、乘、除
这三个运算符只适用于数字,所以适用于将两边操作数同时调用toNumber转换为数字后再运算
console.log([]-[]) // 0
console.log([]-{}) // NaN
console.log([] - '11') // -11
还有几个一元运算符,也是直接应用toNumber转换后在运算,++
,--
,+
,-
console.log(+[]); // 0
console.log(-[]); // -0
let a = [];
let b = {};
a++;
b--;
console.log(a) // 1
console.log(b) // NaN
const obj = {
valueOf () {
return 10;
},
toString () {
return 100;
}
}
console.log(+obj); // 10
在加减乘除进行运算的时候有一个坑会被经常提到,就是
[]+{}
和{}+[]
放回的是不同的结果,刚开始我也很懵逼,拆了下资料后才发现是代码块在搞鬼,原来在js中,{}
不仅可以被当成对象字面量,还可以被看作代码块,在前面没有任何操作符的情况下,就被当做了代码块,所以{}+[]
其实相当于+[]
,执行的是toNumber操作,所以结果为0
宽松相等和严格相等
在js类型中还有一个比较坑的点就是宽松相等的时候的转换,关于宽松相等和严格相等,常见的误区,包括我之前也有,就是“==检查值是否相等,===检查值和类型是否相等”,正确的解释应该是“==允许在相等比较中进行强制类型转换,而===不允许”。如果比较的两个值类型相同。则在处理上几乎无差别,而如果两边值类型不同,===不会做类型转换,==某些情况下会做类型转换
字符串和数字之间
在es5中规范是这样定义
- 如果Type(x)是数字,Type(y)是字符串,则返回x == toNumber(y)的结果
- 如果Type(x)是字符串,Type(y)是数字,则返回toNumber(x) == y的结果 就是将字符串转为数字后再比较,我们可以用NaN这个特殊的数字来验证下
console.log(NaN == 'NaN') // false
这里如果是数字NaN转为字符串'NaN'最后的结果应该是true
其它值和布尔值之间的比较
在说布尔值之前我们先看一个例子
console.log('42' == true); // false
console.log('42' == false); // false
有没有一种离谱的感觉,一个值非真也非假,关于布尔值和其它的的比较,在es规范中是这样说的
- 如果Type(x)是布尔值,则返回toNumber(x) == y的结果
- 如果Type(y)是布尔值,则返回x == toNumber(y)的结果
'42' == true
分析步骤如下 '42' == toNumber(true)
得到结果'42' == 1
toNumber('42') == 1
得到结果42 == 1
- 得到结果false
'42' == false
的转换也一样,先将false转为0,再将'42'转为42最后得到42==0
为false
鉴于这种隐晦的规则,个人强烈不建议在开发中用到==布尔值这种判断,一不小心可能就掉坑里了
null和undefined之间的比较
在==中,null和undefined相等,同时他们与自身相等, 除此之外,与任何值不相等
const a = null;
const b = undefined;
console.log(a == b) // true;
console.log(a == null) // true;
console.log(b == undefined) // true;
console.log(b == false) // false;
console.log(a == false) // false;
这一点在开发中很实用,比如我们要判断一个变量既不是null也不是undefined的时候,就可以很使用
if (a == null) {
....
}
//相当于
if (a === null || a === undefined) {
....
}
对象和非对象之间的相等比较
关于对象和基本类型之间的比较,es规范中定义如下
- 如果Type(x)是基本类型,Type(y)是对象,则返回x == toPrimitive(y)的结果
- 如果Type(x)是对象,Type(y)是基本类型,则返回toPrimitive(x) == x的结果
举例说明一下
console.log(42 == [42]) // true
console.log([] == false) // true
42 == [42]
- 首先
42 == toPrimitive([42])
得到42 == '42'
; - 然后
42 == toNumber('42')
最后得到结果 true;
[] == false
- 首先
toPrimitive([]) == false
得到'' == false
; - 然后
'' == toNumber(false)
得到'' == 0
; - 再
toNumer('') == 0
得到结果0==0
返回true
抽象关系比较
如果你到这里已经感觉已经有点晕了,那也无妨,下面再来一点,加深眩晕效果,关于抽象关系比较,es规范中给出的标准是这样的
- 比较双方调用toPrimitive,
- 如果结果类型都是字符串或数字,返回比较结果,
- 如果一个字符串一个数字,调用toNumber转为数字后再返回结果
const a = [42];
const b = ['43'];
console.log(a < b) // true
console.log(a > b) // false
有一个比较坑的点是两个对象相比较的时候会出现有趣的结果
const a = {};
const b = {};
console.log(a > b); // false
console.log(a >= b); // true
console.log(a == b); // false
是不有有一种被数学欺骗了的感觉,其实仔细一分析就能知道为啥,对象调用toPrimitive转换后得到的结果都是"[object Object]"
所以>=
的时候是相等的,而在==
比较的时候,因为两边都是对象,且并非指向同一个,所以反悔了false;
总结
JavaScript中的类型转换是被很多人诟病的地方,隐式转换往往发生在一些操作的副作用中,难为人所发现,如果掌握不好,会给我们带来一些隐藏的风险!但是只要我们掌握了其中的规则,还是能找到一些精华的地方
参考资料
《你不知道的JavaScript 中卷》