在平时的积累过程中有没有遇见过类似这样的代码:
1 | []+[] // '' |
虽然代码中不提倡写这种晦涩难懂还说不定就出错的代码,但是其原理我们还是要懂得
那么下面可以开始记录一下 JavaScript 代码世界中的隐式转换。
加法运算
在 JavaScript 中加法运算很简单,它只做 数字和字符串 的加法操作,所以不是这两种类型的都会被转换成这两种原始类型数据再进行操作。
而在 JavaScript 中数据类型分为两种(原始数据类型和复杂数据类型):
原始数据类型 (primitives): undefined, null, number, string, boolean, symbol(es6新增)
复杂数据类型: 对象(包括数组、函数)
那么对象是怎么转换成原始数据类型的呢?
ToPrimitive 方法
在 JavaScript 内部有一个 ToPrimitive() 方法,它用于将对象转换成原始数据类型。
1 | ToPrimitive(input, PreferredType?) |
这个函数接收两个参数:
input: 输入值
PreferredType: 此参数可以是 Number 或者 String,它代表我们的对象会优先转换成哪种原始数据类型。
如果缺少第二个参数,对于 Date 的实例,默认为 String,其余的都为 Number; 转换步骤也会因为这个参数的不同而不同。
下面看一下它的转换过程。
PreferredType 为 Number
input
为原始数据类型,直接返回input
;- 如果
input
是一个对象,那么它会先去调对象的valueOf()
方法,得到的结果若为原始数据类型则返回; - 如果上一步得到的结果仍然是对象,那么此时会去调用对象的
toString()
方法,得到的结果为原始数据类型则返回; - 如果上一步操作完之后得到的仍不是原始数据类型,那么此时就抛出错误。
PreferredType 为 String
input
为原始数据类型,直接返回input
;- 如果
input
是一个对象,那么它会先去调对象的toString()
方法,得到的结果若为原始数据类型则返回; - 如果上一步得到的结果仍然是对象,那么此时会去调用对象的
valueOf()
方法,得到的结果为原始数据类型则返回; - 如果上一步操作完之后得到的仍不是原始数据类型,那么此时就抛出*错误。
这里的 PreferredType 为 Number 和 String 两个不同值处理转换的步骤略有不同,主要区别就是根据数据类型判断先执行 valueOf() 方法还是 toString() 方法,交换上面执行步骤的2、3步。
那么 valueOf() 和 toString() 究竟返回的是什么?
valueOf() 和 toString()
valueOf() 和 toString() 都是 Object 的属性,那它们返回的都是什么?例子:
1 | const data1 = { |
总结:
对象: valueOf() 返回对象本身,toString() 返回字符串 [object Object];
数组: valueOf() 返回数组本身,toString() 返回数组值用逗号链接的字符串,和 join(‘,’) 得到的结果一样;
方法: valueOf() 返回方法本身,toString() 返回的是Function中改写的toString()处理完的字符串结果;
+ 运算
最后回归正题,加号(+)的运算,就是对当前进行加法运算的值进行转换后再做相应的操作,如:
1 | value1 + value2 |
在上面的加法运算表达式中,会是如下操作步骤:
1.将两个操作数转换成基本数据类型
1 | prim1 = ToPrimitive(value1); |
2.如果 prim1 和 prim2 是一个字符串,则将其转成字符串并返回相连接的结果
3.否则,将 prim1 和 prim2 都转为数字并返回结果的综合
那么最开始的代码
1 | []+[] |
进行运算后得到的结果是什么呢?
[]+[]
- 首先将[]进行原始数据转换,也就是 ToPrimitive([]),因为是数组,所以 PreferredType 为 Number
- 经过转换步骤2([].valueOf())后仍是一个对象
- 那么再进行步骤3([].toString()),得到的结果就是一个空字符串
- 两个空字符串相加还是一个空字符串(””)
{}+{}
- 首先将{}进行原始数据转换,也就是 ToPrimitive({}),因为是对象,所以 PreferredType 为 Number
- 经过转换步骤2([].toString())后仍是一个字符串”[object Object]”,已经是原始数据类型,不需要在进行转换
- 那么得到的结果就是两个字符串相加的结果(”[object Object][object Object]”)
1+[]
- 首先对1和[]进行原始数据转换,也就是 ToPrimitive(1) 和 ToPrimitive([]),因为1已经是原始数据类型,则直接返回值1
- 而[]是数组,则 PreferredType 为 Number,经过步骤2([].valueOf())后仍是一个对象,再进行步骤3([].toString()),得到了一个空字符串
- 最后,就是 1+”” ,得到的结果是字符串 “1”
而我在进行测试的时候发现了一个有意思的事情是:
{}+[] 和 {}+1 得到的结果并不是 “[object Object]” 和 “[object Object]1”
根据我们前面的操作,这里应该就是这个结果,那这是为什么呢?
最后发现这是不同的浏览器的解析不同,它会将前面的 {} 当成一个代码块,于是得到的结果就会变成 +0 和 +1,即为 0 和 1。
- 本文作者: Jambo
- 版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!