加法操作符
September 18, 2018
引子
JavaScript
上手快 1 年了,今天无意刷到了几道传统面试题,无法有条理的给出答案,顿感基础薄弱。 这几道题都是关于加法操作符的知识点,先上面试例题:
1. []+[] //''
2. []+{}//'[object Object]'
3. {}+[]//0
4. {}+{}//'[object Object][object Object]'
下面先总结加法操作符规则,之后按照总结的计算规则,依次解题。
+操作符规则
"+"作为二元操作符,完成表达式x+y
中 2 个变量数值相加或字符串拼接,具体规则如下所示:
- 如果 x, y 至少有一个为对象,将其转换为基本类型
- 转换后如果 x, y 至少有一个为
string
,则将另一个转换为string
,执行字符串拼接 - 以上均不满足,将 x, y 转换为
number
,执行数值计算
对象转换为基本类型规则
- 如果 x 是
Date
对象,返回x.toString()
- 如果 x 是其他对象,且
x.valueOf()
返回值为基本类型,返回x.valueOf()
- 如果
x.valueOf()
返回值不是基本类型,且 x.toString()返回值为基本类型,返回x.toString()
-
以上皆不满足,抛出
TypeError
异常注:根据 ECMA,对象到基本类型转换实际过程较复杂,以后单独写一篇
题目解析
题 1
[] + []; //''
分析:
- 均为对象,先将其转换为基本类型,根据对象转换规则,数组首先调用 valueOf(),返回值为自身,继续调用 toString(),返回空字符串'',表达式变为''+''
- 根据+规则-2,执行字符串拼接,表达式返回结果''
题 2
[] + {}; //'[object Object]'
分析:
- 均为对象,先将其转换为基本类型,根据对象转换规则,首先调用
valueOf()
,返回值为自身,继续调用toString()
,返回空字符串''
和'[object Object]'
,表达式变为''+'[object Object]'
- 根据+规则-2,执行字符串拼接,表达式返回结果
'[object Object]'
题 3
{
}
+[]; //0
分析:
- 因浏览器将
{}
解析为代码块注[1],表达式等价于+[]
,先将其转换为基本类型,根据对象转换规则,数组首先调用valueOf()
,返回值为自身,继续调用toString()
,返回空字符串''
,表达式变为+''
,取Number('')
,表达式返回0
- 如果将表达式改写为
({})+[]
,将得到与题 2 相同的结果
题 4
{
}
+{}; //'[object Object][object Object]'
分析:
- 这道题比较奇怪
- 按照题 3 的规则,第一个
{}
将被解析为代码块,表达式等效于+{}
,按照对象转换规则,先变成+'[object Object]'
,然后取Number('[object Object]')
,应该返回结果NaN
才对。FireFox 62
,Edge
,IE11
的输出就是这个。 - 可是输入
Chrome 69
,二者都是被当作对象处理的,最终表达式返回结果'[object Object][object Object]'
- 看来几家浏览器在这种情况下规则有所不同,深入研究需要看
parser
里的抽象语法树Abstract Syntax Tree(AST)
是如何解析的,以后有时间了再研究
下面对题 4 做一点扩展,算作验证
题 4-扩展
1. ({})+{} //Chrome - '[object Object][object Object]'
//FireFox - '[object Object][object Object]'
//IE 11 - '[object Object][object Object]'
//Edge - '[object Object][object Object]'
2. {}+({}) //Chrome - NaN
//FireFox - NaN
//IE 11 - NaN
//Edge - NaN
3. {}+{}+{} //Chrome - '[object Object][object Object][object Object]'
//FireFox - 'NaN[object Object]'
//IE 11 - 'NaN[object Object]'
//Edge - 'NaN[object Object]'
4. ({})+{}+{}//Chrome - '[object Object][object Object][object Object]'
//FireFox - '[object Object][object Object][object Object]'
//IE 11 - '[object Object][object Object][object Object]'
//Edge - '[object Object][object Object][object Object]'
5. {}+({})+{}//Chrome - '[object Object][object Object][object Object]'
//FireFox - 'NaN[object Object]'
//IE 11 - 'NaN[object Object]'
//Edge - 'NaN[object Object]'
6. {}+{}+({})//Chrome - 'NaN[object Object]'
//FireFox - 'NaN[object Object]'
//IE 11 - 'NaN[object Object]'
//Edge - 'NaN[object Object]'
7. {}+[]+{} //Chrome - '[object Object][object Object]'
//FireFox - '0[object Object]'
//IE 11 - '0[object Object]'
//Edge - '0[object Object]'
8. {}+{}+[] //Chrome - 'NaN'
//FireFox - 'NaN'
//IE 11 - 'NaN'
//Edge - 'NaN'
看起来似乎Chrome
对以{}
开头且以{}
结尾(即'{}+...+{}'
) 的表达式做了优化解析,不会把遇见的{}
视为代码块,而是作为空对象解析。其他浏览器没有这个规则。
注 1
Expression Statement Syntax
ExpressionStatement[Yield] : [lookahead ∉ {{, function, class, let [}] Expression[In, ?Yield] ;
NOTE An ExpressionStatement cannot start with a U+007B (LEFT CURLY BRACKET) because that might make it ambiguous with a Block. Also, an ExpressionStatement cannot start with the function or class keywords because that would make it ambiguous with a FunctionDeclaration, a GeneratorDeclaration, or a ClassDeclaration. An ExpressionStatement cannot start with the two token sequence let [ because that would make it ambiguous with a let LexicalDeclaration whose first LexicalBinding was an ArrayBindingPattern.