首先从一系列让JavaScript
初学者抓狂的运算提及。javascript
1 + {} {} + 1 [] + {} {} + [] [] + [] {} + {}
能所有答对上面的运算结果,没必要浪费时间继续阅读本文了。
若是对某一些的结果还不肯定,请慢慢往下看。java
上面列的全部运算,须要理清如下两点:express
+
和{}
的解析规则;JavaScript
是如何进行类型转换的;+
和{}
的解析规则+
符号{}
符号首先,咱们来了解,+
符号做为加号二元运算符的运算规则数组
ToPrimitive
转换左右运算元为原始数据类型值;1
步转换后,若是有运算元出现原始数据类型为“字符串”类型时,则另外一运算元强制转换为字符串,而后作字符串链接运算;ToPrimitive
运算从上图总结ToPrimitive
运算的语法说明:浏览器
ToPrimitive(input, PreferredType?)
input
表明代入的值,而PreferredType
能够是数字(Number
)或字符串(String
)其中一种,这会表明“优先的”、“首选的”的要进行转换到哪种原始类型,转换的步骤会依这里的值而有所不一样。
但若是没有提供这个值也就是预设状况,则会设置转换的hint
值为default
。这个首选的转换原始类型的指示(hint
值),是在做内部转换时由JS
视状况自动加上的,通常状况就是预设值。ide
PreferredType
为数字(Number
)时转换步骤为:spa
input
是原始数据类型,则直接返回input
。input
是个对象时,则调用对象的valueOf()
方法,若是能获得原始数据类型的值,则返回这个值。input
是个对象时,调用对象的toString()
方法,若是能获得原始数据类型的值,则返回这个值。TypeError
错误。PreferredType
为字符串(String
)时上面的步骤2与3对调,转换步骤为:3d
input
是原始数据类型,则直接返回input
。input
是个对象时,则调用对象的toString
方法,若是能获得原始数据类型的值,则返回这个值。input
是个对象时,调用对象的valueOf()
方法,若是能获得原始数据类型的值,则返回这个值。TypeError
错误。有几点值得注意:code
PreferredType
;valueOf
再调用toString
;例外:Date 对象、Symbol 对象对象
a + b: pa = ToPrimitive(a) pb = ToPrimitive(b) if(pa is string || pb is string) return concat(ToString(pa), ToString(pb)) else return add(ToNumber(pa), ToNumber(pb))
V8
实现源码// ECMA-262, section 9.1, page 30. Use null/undefined for no hint, // (1) for number hint, and (2) for string hint. function ToPrimitive(x, hint) { if (!IS_SPEC_OBJECT(x)) return x; if (hint == NO_HINT) hint = (IS_DATE(x)) ? STRING_HINT : NUMBER_HINT; return (hint == NUMBER_HINT) ? DefaultNumber(x) : DefaultString(x); }
Date
类型的对象预设值为字符串(String
)。DefaultNumber
和DefaultString
方法
// ECMA-262, section 8.6.2.6, page 28. function DefaultString(x) { if (!IS_SYMBOL_WRAPPER(x)) { if (IS_SYMBOL(x)) throw MakeTypeError(kSymbolToString); var toString = x.toString; if (IS_SPEC_FUNCTION(toString)) { var s = % _CallFunction(x, toString); if (IsPrimitive(s)) return s; } var valueOf = x.valueOf; if (IS_SPEC_FUNCTION(valueOf)) { var v = % _CallFunction(x, valueOf); if (IsPrimitive(v)) return v; } } throw MakeTypeError(kCannotConvertToPrimitive); }
// ECMA-262, section 8.6.2.6, page 28. function DefaultNumber(x) { var valueOf = x.valueOf; if (IS_SPEC_FUNCTION(valueOf)) { var v = % _CallFunction(x, valueOf); if (IS_SYMBOL(v)) throw MakeTypeError(kSymbolToNumber); if (IS_SIMD_VALUE(x)) throw MakeTypeError(kSimdToNumber); if (IsPrimitive(v)) return v; } var toString = x.toString; if (IS_SPEC_FUNCTION(toString)) { var s = % _CallFunction(x, toString); if (IsPrimitive(s)) return s; } throw MakeTypeError(kCannotConvertToPrimitive); }
至此,咱们弄清楚了ToPrimitive
内部运算规则。
对于原始数据类型直接的转换规则就不一一解释了,能够参看下表:
1 + {}
解析为数字1
与空对象{}
进行加运算,按照上面的规则,空对象{}
按照valueOf
-> toString
顺序,valueOf
返回对象自己,因此调用toString
返回"[object Object]"
为字符串,根据规则,有一个为字符串,则进行字符串链接运算,数字1
强制转换为字符串"1"
,最终结果为"1[object Object]"
。
{} + 1
这个例子有坑,由于对于浏览器引擎来讲,它们会认为以花括号开头{
的,是一个 区块语句 的开头,而不是一个对象字面量的语句,因此会认为略过第一个{}
。因此这个例子至关于+1
,加号运算会直接变为一元正号运算,对数字1
强制转换为数字类型,结果为1
。
[] + {}
将[]
和{}
分别调用ToPrimitive
方法,返回""
和"[object Object]"
,而后作字符串拼接获得"[object Object]"
。
{} + []
开始的{}
被解析为区块语句而略过,至关于+[]
,而[]
转为原始数据类型结果为""
,强制转换""
为数字类型,结果为0
;
[] + []
将[]
和{}
分别调用ToPrimitive
方法,返回""
和""
,而后作字符串拼接获得""
。
{} + {}
这个例子有争议,对于Firefox
和Edge
浏览器来讲,他们一以贯之的将第一个{}
做为区块语句略过,而对于V8
引擎系列(Chrome
、Node.js
等)则将第一个{}
解析为对象字面量。
这样致使结果不一致,Firefox
等解析语句为+{}
,对空对象{}
强制转为数字类型,即为+"[object Object]"
,将非空字符串转换为数字类型,结果为NaN
。Chrome
等解析语句为两个空对象作加运算,结果也比较好理解:"[object Object][object Object]"
。
[] == ![]
这是一个不严格等于运算,咱们来看转换过程。
// 第一步,转换右边,根据上述原始数据类型转换规则表, // 全部对象强制转 Boolean 类型都是 true,因此 ![] 为 false ToPrimitive(![]) >> ToPrimitive(!ToBoolean([])) >> ToPrimitive(!true) >> ToPrimitive(false) >> 0 // 第二步,转换左边 ToPrimitive([]) >> "" // 第三步,判断 "" == 0 >> ToNumber("") == 0 >> 0 == 0 >> return true
++[[]][+[]] + [+[]]
进一步,来看这个例子。
很明显,根据运算符优先级,这个表达式能够用+
分隔为左右两个部分++[[]][+[]]
和[+[]]
。
[+[]]
能够看出这是一个数组里面有一个元素+[]
,而+[]
即将[]
强制转换为数字类型,因此等于+""
,结果为0
。
综上,右边表达式转换为[0]
。
++[[]][+[]]
咱们来一步步拆解, 根据对右边表达式的转换,这个表达式能够等同看作++([[]][0])
,++
后面又能够看作数组去第1
个元素,表达式转换为++[]
。
可是当咱们去浏览器执行++[]
时,报错了:<font color=red>Uncaught ReferenceError: Invalid left-hand side expression in prefix operation</font>。
吓得我赶忙去看++
的语法,原来++
的运算是一种引用运算,即++[]
应该转换为:
var ref = [] ref = ref + 1
因此++[]
转换的正确姿式为[] + 1
。
左右进行相加获得:[] + 1 + [0]
。
根据ToPrimitive
运算规则,[] + 1 + [0] === "" + 1 + [0] === "1" + [0] === "10"
。