本文旨在让读者能更加清晰地了解 “空对象 {}” 与 “空数组 ()” 相加(eg:“[] + []” 、“[] + {}”、“{} + []”、“{} + {}”)得到结果的隐式转换过程,还望读者能仔细阅读完,相信或多或少会有所收获 !
首先来了解一些相关知识
一、在 JavaScript 中,一共有两种类型的值:
- 原始值(primitives)
undefined、null、boolean、number、string - 对象值(objects)
除了原始值外,其他的所有值都是对象类型的值,包括数组(array)和函数(function)。
二、“加号 +” 的运算原理( “+” 既是一元运算符,也是二元运算符)
1. 一元运算符
说明:’+’ 号运算符作为一元运算符时,表达式将进行 ToNumber()
操作(隐式类型转换)。
注:有关隐式类型转换,可参考 Javascript基础:隐式类型转换
① ToNumber(argument) 转换方式:
argument类型 | 返回值 |
---|---|
Undefined | return NaN |
Null | return 0 |
Boolean | true return 1; false return 0; |
Number | return value |
String | 若字符串为纯数字,返回转换后的数字;空字符则返回 0;非纯数字则返回 NaN |
Symbol | 抛出 TypeError 异常 |
Object | 进行如右步骤:1.先进行 ToPrimitive(argument, hint Number) 得到 rs ; 2.然后返回 ToNumber(rs) 的结果。 |
② 示例:
// Undefined
+ undefined; // => NaN
// Null
+ null; // => 0
// Boolean
+ true; // => 1
+ false; // => 0
// String
+ '1'; // => 1
+ '-1'; // => -1
+ 'a1'; // => NaN
// Object
+ []; // => 0
// ToPrimitive([]) -> ''
// ToNumber('') -> 0
+ {}; // => NaN
+ { valueOf: function () { return 0 } }; // => 0
/*
* 该对象重写了 valueOf() 方法,
* 因此该对象隐式转换过程中调用 valueOf() 方法后得到 0,
* 故最终结果为 0
*/
2. 二元运算符
① 运算元其一为字符串(String)
运算元其一为字符串时,进行字符串的拼接操作。
console.log('10' + 1); // 101
console.log('ab' + 'cd'); // abcd
console.log('1' + true); // 1true
console.log('1' + undefined); // 1undefined
console.log('1' + null); // 1null
② 运算元其一为数字(Number)
1 + ‘cd’ 为运算元其一为字符串情况,做字符串拼接操作;其余为在没有字符串情况下,运算元其一为数字,做类型转换后做数值相加。
console.log(1 + 1); // 2
console.log(1 + 'cd'); // 1cd
console.log(1 + true); // 2
console.log(1 + undefined); // NaN
console.log(1 + null); // 1
注:在运算元一侧为数字,另一侧为字符串的情况下,如果是 “减号 - ” 或其他运算符(eg:“*”、“/”、“%”、“ >”、“<”、“==”) 的话,会将字符串转换为数字进行减法操作(eg:数字相减:10 - “1” = 9)。
③ 数字(Number) / 字符串(String) 以外的原始类型相加
当数字与字符串以外的其他原始数据类型直接使用加号运算时,要先转为数字(可参考上面有关 “ToNumber(argument) 转换方式” 的表格)再运算,这与字符串完全无关。
console.log(true + true); // 2
console.log(true + null); // 1
console.log(true + undefined); //NaN
console.log(undefined + null); //NaN
console.log(undefined + undefined); //NaN
console.log(null + null); //0
④ 运算元其一为复杂数据类型(以下为初步解释,示例请见后续第三大点)
注意,以上 ① ~ ③ 的示例均是原始数据类型的 “加法 +” 操作,当进行复杂数据类型的 “加法 +” 操作时,JS 内部有以下有隐式转换过程(实际上是 JS 调用了内部的 toPrimitive() 方法,有关该方法详见 JS原始值转换算法—toPrimitive() ):
Ⅰ 当 “加号 +” 的某一侧数据类型是对象时,会将对象先进行 valueOf() 操作(有关 valuOf() 方法可参考博文 JS 中 valueOf() 方法的详解);
Ⅱ 如果返回的是原始数据类型,则后续操作按照以上三点进行;
Ⅲ 如果返回的值还是对象,则再调用 toString 方法(此处应了解 “ [] 调用 toString() 方法变成空字符串 ""
,{} 调用 toString() 等于 [object Object]
” ,有关 toString 方法详解可参考 有关 toString() 方法的初步认识 );
Ⅳ 若返回原始数据类型,则按照上面原始数据类型计算;
Ⅴ 否则报错。
⑤ 有关 NaN 需要注意的点
Ⅰ. 执行运算时 , 非带 “+” 号的运算,只要有 NaN 参与,执行结果就都是 NaN;
Ⅱ. 如果是带 “+” 号的运算,一侧是 NaN、另一侧是字符串时,就执行字符串拼接操作;
Ⅲ. 如果是带 “+” 号的运算,一侧是 NaN、另一侧是 Number 类型的数值时,执行结果就都是 NaN;
Ⅳ. 在 JavaScript 中的规定,NaN 表示的是非数字,但是这个非数字也是不同的;因此 NaN 不等于 NaN,两个 NaN 永远不可能相等。
注:NaN 虽然不是一个具体数值,但数据类型确是 Number 类型;NaN 和任何 Number 类型数据进行 “+”、“-”、“*”、“/”、“% 等操作时,操作结果都是 NaN。
三、“空对象 {}” 与 “空数组 []” 的相加问题
1. 空数组 + 空数组
隐式转换过程: 首先 []
调用 valueOf() 方法,得到的还是 []
,然后调用 toString() 方法,得到 ""
,两个 ""
相加还是 ""
,故最终结果为 ""
。
2. 空数组 + 空对象
隐式转换过程: 如第一点分析,[]
经隐式转换最终得到 ""
;{}
调用 valueOf() 方法,得到的还是 {}
,然后调用 toString() 方法,得到 "[object Object]"
,""
和 "[object Object]"
相加得到 "[object Object]"
,故最终结果为 "[object Object]"
。
3. 空对象 + 空对象
隐式转换过程: 分析与上面类似,此处不再累赘
4. 空对象 + 空数组
补充有关知识:
首先要知道 javascript 有这样的特性,如果 {}
既可以被认为是代码块,又可以被认为是对象字面量,那么 js 会把他当做代码块 (经测试, 除了 {} + {}
时,{}
不会被当成代码块外,{}
+ 其他类型的数据时都会被当作代码块 );
而在浏览器中,如果 {}
在前面,而 []
在后面时,前面的 {}
会被认为是区块语句而不是对象字面量;
故此处的 {} + []
可看成 + []
,然后参考上面有关 “ToNumber(argument)转换方式” 的表格中 argument 类型为 Null 和 Object 的两行,对本例进行分析。
隐式转换过程:
① 参考表格中 argument 类型为 Object 的一行,对于 + []
来说,对 []
进行 ToPrimitive(argument, hint Number) 操作【可参考第二大点的第二小点最下方的注释】,先对 []
调用 valueOf() 方法,得到的还是 []
,然后调用 toString() 方法,得到 ""
;
② 参考表格中 argument 类型为 String 的一行,故 + ""
返回的是 0 。
综上,最终 {} + []
得到的结果是 0
5. !空数组 + 空数组
补充有关知识:
根据运算符优先级 , ! 的优先级是大于 + 的,所以先会执行 ![] ;
! 可将变量转换成 boolean 类型,且除 0
、NaN
、null
、undefined
以及 ""
取反为 true 外,其余运算元取反都为 false 。
隐式转换过程:
① 根据以上分析,先执行 ![]
,得到 false
② []
经隐式转换最终得到 ""
③ 参考第二大点的第二小点中的示例(如下),可知 false + ""
的结果为 "false"
console.log('1' + true); // 1true
6. !空数组 + 空对象
隐式转换过程:
① 执行 ![]
,得到 false
② {}
经隐式转换得到 "[object Object]"
③ 可知 false + "[object Object]"
的结果为 "false[object Object]"
7. !空对象 + 空对象
隐式转换过程:
① 执行 !{}
,得到 false
② {}
经隐式转换得到 "[object Object]"
③ 可知 false + "[object Object]"
的结果为 "false[object Object]"
8. !空对象 + 空数组
隐式转换过程:
① 执行 !{}
,得到 false
② []
经隐式转换得到 ""
③ 可知 false + ""
的结果为 "false"