首先從一系列讓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"
。