今天看到几道有关加号的题目,觉得很怪异如下
| [] + {} // '[object Object]' | 
当时我就完全没有搞懂,所以决定探索一下 JavaScript 中的 + 号运算符,另外,对于 toString 和 valueOf 这两个方法一直搞不清会调用哪个,在探索 + 号运算符的过程中也一并搞懂了,我将会在下面仔细讲解。
一元运算符
+ 既可以作为一元运算符,也可以作为二元运算符,首先我们先讲解较简单的一元运算符,如下表:
| 类型 | 转换规则 | 
|---|---|
| undefiend | NaN | 
| null | 0 | 
| boolean | true => 1false => 0 | 
| number | 原样返回,如 +1 => 1 | 
| string | 1. 如果字符串为纯数字组成,如 "5678" => 56782. 如果不为纯数字,那么返回 NaN3. 空字符串会被转化为 0,即"" => 0 | 
| symbol | 抛出 TypeError异常 | 
| object | 分为两步: 1. 先进行 toPrimitive转化为基本数据类型,得到返回值ret2. 然后对 ret进行上面描述过程的转换,例如如果返回true,得到1 | 
下表是 ECMAScript 中的规范,上面的内容来自这里:
| Argument | Result | 
|---|---|
| Undefined | NaN | 
| Null | +0 | 
| Boolean | The result is 1 if the argument is true.The result is +0 if the argument is false. | 
| Number | The result equals the input argument (no conversion). | 
| String | See grammar and note below. | 
| Object | Apply the following steps:1. Let primValue be ToPrimitive(input argument, hint Number).2. Return ToNumber(primValue). | 
下面来看几个例子,来验证上面的内容
| console.log(+undefined); // NaN | 
对于对象首先要进行 toPrimitive 转化为原始类型,然后将原始类型转化为数字
| let obj1 = { | 
对于空对象,在转化为原始值时得到的是 "[object Object]",将它转化为数字时,这个字符串不是纯数字,所以会被转化为 NaN。
二元运算符
当把 + 作为二元运算符时,遵循以下过程:
- Let lref be the result of evaluating AdditiveExpression.
- Let lval be GetValue(lref).
- ReturnIfAbrupt(lval).
- Let rref be the result of evaluating MultiplicativeExpression.
- Let rval be GetValue(rref).
- ReturnIfAbrupt(rval).
- Let lprim be ToPrimitive(lval).
- ReturnIfAbrupt(lprim).
- Let rprim be ToPrimitive(rval).
- ReturnIfAbrupt(rprim).
- If Type(lprim) is String or Type(rprim) is String, then- Let lstr be ToString(lprim).
- ReturnIfAbrupt(lstr).
- Let rstr be ToString(rprim).
- ReturnIfAbrupt(rstr).
- Return the String that is the result of concatenating lstr and rstr.
 
- Let lnum be ToNumber(lprim).
- ReturnIfAbrupt(lnum).
- Let rnum be ToNumber(rprim).
- ReturnIfAbrupt(rnum).
- Return the result of applying the addition operation to lnum and rnum. See the Note below 12.7.5.
上面是 ECMAScript 中的规范,如果英文不太熟的话,下面我将以中文简单翻译如下:
- 首先将 +号两边的值通过toPrimitive转化为基本数据类型(如果已经是基本数据类型,则原样返回)
- 如果得到的两个基本数据类型中有字符串,那么将二者转化为字符串拼接起来,将结果返回
- 如果两个基本数据类型中没有字符串,那么就将两个值转化为数字,然后进行相加
下面来看几个例子:
| // 两个值中有一个为字符串,则调用二者的 toString 方法,然后进行相加 | 
对于对象要转化为原始类型,然后进行相加,那么对象转化为原始类型的过程是什么? 下面是调用的过程:
- 如果有 [Symbol.toPrimitive]方法,则调用[Symbol.toPrimitive]()方法转化为原始类型,该方法的返回必须为原始类型
- 如果没有 [Symbol.toPrimitive]方法,那么调用valueOf方法,如果valueOf返回的不是原始值(基本数据类型),那么就调用toString方法,如果toString返回的也不是原始值,那么就会报错
| [] + 2; // "" + 2 = "2" | 
数组也是对象,因为数组没有 [Symbol.toPrimitive] 方法,所以首先会调用数组的 valueOf 方法,因为数组的 valueOf 方法返回的是数组本身,并不是基本数据类型,所以接着会调用数组的 toString 方法,得到一个空字符串 "" ,通过我们上面的讲解,如果两个值中有一个是字符串的话,则会将二者转化为字符串进行拼接,所以 [] + 2 => "" + 2 = "2"。下面在来看一个例子:
| let obj = { | 
由于对象 obj 有 [Symbol.toPrimitive] 方法,所以在转化为基本数据类型时会调用该方法,得到的值为 20,所以 2 + obj => 2 + 20 = 22。
题目讲解
回到开头我们提出的几个例子:
| [] + {} // '[object Object]' | 
首先将两个东西转化为基本数据类型,因为它们都没有 [Symbol.toPrimitive] 方法,所以接着会调用它们的 valueOf 方法,但是它们的 valueOf 方法返回的都是它们本身,所以接着会调用它们的 toString 方法,[] 的 toString 方法得到的是 '' 空字符串,{} 的 toString 方法得到的是 '[object Object]',二者都是字符串,将二者进行拼接,得到的结果是 '[object Object]'。
| [] + [] // '' | 
有上题的经验,[] => '',两个空字符串进行拼接得到的结果是 ''。
| {} + [] // 0 | 
接着看 {} + [],因为 JavaScript 会将以 { 开头的语句解析为代码块而不是一个空对象,所以 {} + [] 相当于 +[],这时的 + 相当于是一个一元运算符,根据一元运算符上面讲解的内容,首先将 [] 转化为基本数据类型,得到 "",接着将 "" 转化为数字,得到的结果为 0。
| {} + {} // NaN | 
根据上一道题的讲解,{} + {} 相当于 +{},{} 转换为基本数据类型得到的是 "[object Object]",该字符串转换为数字得到的 NaN,所以结果是 NaN。
| [] + {} == {} + [] // true | 
[] + {} 得到的结果是 '[object Object]',{} + [] 得到的结果并不是 0,因为这时 {} 并不是在语句的开头,会被看做是空对象,所以 {} + [] 得到的结果也是 '[object Object]',二者是相等的,结果是 true
| {} + [] != [] + {} // true | 
{} + [] 因为在开头,得到的结果是 0,而 [] + {} 的结果是 [object Object],二者不相等,所以 {} + [] != [] + {} 的结果也是 true。










