很差意思作了一回標題黨,JavaScript 中歷來就沒有什麼奇怪的事件,我只是想梳理一下 javascript 中讓人疑惑的表達式以及背後的原理。javascript
好比請說出如下這些表達式的結果:html
1 + '1'
1 - '1'
'2' + '2' - '2'
[] + []
{} + {}
[] + {}
{} + []
[] + {} === {} + []
{} + [] === [] + {}
[+false] + [+false] + [+false]
[+false] + [+false] + [+false] - [+false]
'1' == true
parseInt('infinity') == 0 / 0
1 < 2 < 3
3 > 2 > 1
isNaN(false)
isNaN(null)
[[][[]]+[]][+[]][++[+[]][+[]]]
若是想知道正確答案的話把表達式粘貼到瀏覽器的控制檯執行便可前端
接下來的內容就是講解這些表達式的結果是在什麼樣的原理下得出的java
解決以上的問題的關鍵在於要搞明白三點:chrome
+
操做符+
在 JavaScript 中有三個做用:數組
var result = 'Hello' + 'World'
var result = 1 + 2
+variable
在表達式中+
是操做符(operator),操做符操做的對象(上面例子中的Hello
、 World
、 1
、 2
)名爲操做數(operand)瀏覽器
一元+
操做符的運算規則是:ToNumber(ToPrimitive(operand))
,也就是把任意類型都轉化爲數字類型。ide
當操做數的數據類型不一致時,會根據如下規則進行轉化:code
object
),則須要將它轉化爲基礎類型(primitive
),即字符串、數字或者布爾
Date
類型,那麼調用toString()
方法valueOf()
方法valueof()
方法不存在或者並無返回一個基礎類型,那麼調用toString()
join(',')
方法{}
轉化的結果是 [object Object]
因此根據以上規則,咱們就能解釋:htm
1 + '1'
的結果是 '11'
,由於其中一個是操做數是字符串,因此另外一個操做數也被轉化爲字符串,而且執行字符串鏈接操做[] + []
的結果是 ''
空字符串,由於數組是對象類型,轉化爲基礎類型的結果是空字符串,拼接以後仍然是空字符串[] + {}
的結果是 [object Object]
,由於操做數有對象類型的關係,兩個操做數都須要轉化爲基礎類型,[]
轉化爲基礎類型的結果是''
,{}
轉化爲基礎類型的結果是[object Object]
,最後字符串拼接的結果仍然是[object Object]
接下來咱們說一說值得注意的狀況
{} + []
的結果是0
。由於在這個表達式中,開頭{}
並非空對象的字面量,而是被看成空的代碼塊。事實上這個表達式的值就是+[]
的結果,即Number([].join(','))
,即爲0
更奇怪的是{} + {}
這個表達式,在不一樣的瀏覽器中執行會獲得不一樣的結果。 按照上面的例子,咱們能夠同理推出這個表達式的值其實是+{}
的值,即最後的結果是Number([object Object])
,即NaN
。在 IE 11 中的執行結果倒是是如此,可是若是在 Chrome 中執行,你獲得的結果是 [object Object][object Object]
。
根據 Stackoverflow上的回答 這是由於 Chrome devtools 在執行代碼的時候隱式的給表達式添加了括號()
,實際上執行的代碼是({} + {})
。若是你在 IE 11 中執行({} + {})
,就會獲得[object Object][object Object]
的結果
雖然上面咱們已經明確了 [] + {}
的結果是 [object Object]
,而 {} + []
的結果是0
,可是若是把他們進行比較的話:[] + {} === {} + []
結果會是true
。由於右側的{}
跟隨在===
以後的關係,再也不被認爲是空的代碼塊,而是字面量的空對象,因此兩側的結果都是[object Object]
{} + [] === [] + {}
一樣是一個有歧義的結果,理論上來講表達式的返回值是false
,在 IE 11 中確實如此,可是在 Chrome 的 devtools 中返回 true
,緣由仍然是表達式被放在()
中執行
[+false] + [+false] + [+false]
的結果也可想而知了,+false
的結果是false
轉化爲數字0
,以後[0]
又被轉化爲基礎類型字符串'0'
,因此表達式最後的結果是'000'
-
操做符雖然-
操做符和+
操做符看看上去性質相同,但-
操做符只有一個功能,就是數值上的相減。它會嘗試把非數值類型的操做數轉化爲數值類型,若是轉化的結果是NaN
, 那麼表達式的結果可想而知也就是NaN
,若是所有都轉化成功,則執行減法操做,因此
1 - '1'
實際上執行的是 1 - 1
,結果爲 0
'2' + '2' - '2'
表達式首先要遵循從左至右的執行順序,'2' + '2'
的執行的是字符串拼接,結果是'22'
,在接下來的'22' - '2'
計算中兩個操做數都成功的轉化爲了數字,結果是數字相減的結果20
[+false] + [+false] + [+false] - [+false]
表達式實際上執行的是'000' - '0'
,最後的結果也就是數字0
==
操做符在 JavaScript 中===
稱爲恆等操做符(The identity operator),==
稱爲相等操做符(The equality operator)。由於篇幅關係在這裏咱們簡單的針對題目聊聊後者
若是==
操做符的操做數的數據類型不一樣:
null
,而且另一個操做數是undefined
,他們是相等的true
轉化爲1,false
轉化爲0在進行比較'1' == true
時,首先將true
轉化爲數字1
,此時表達式中同時存在數值和字符串類型,再把字符串'1'
轉化爲數字1
,最終1 == 1
固然成立parseInt('infinity') == 0 / 0
其實是在判斷NaN == NaN
,這樣的比較是一個特例,不管是在==
比較仍是===
比較中,NaN
不會與任何東西相等;或者說只要有任意操做數是NaN
,那麼表達式就會返回false
更全面==
和===
的比較規則請參考: The legend of JavaScript equality operator
比較運算符>
和<
也遵循類似的規則: 1. 優先將字符串轉化爲數字進行比較;2. 將布爾類型轉化爲數字再進行比較,
1 < 2 < 3
中,首先執行1 < 2
,結果爲true
,可是在比較true < 3
的過程當中,須要把true
轉化爲數值類型1
,最終比較1 < 3
,返回值爲 true
3 > 2 > 1
中,最終比較的實際上是true > 1
,也便是1 > 1
固然返回的是false
isNaN
"NaN"是"Not a Number"的縮寫,咱們覺得isNaN
可以直接用來判斷值是不是數字類型,但實際上並不能夠。由於isNaN
首先會強制將參數轉化爲數值類型,再進行判斷。 這也就不難解釋爲何isNaN(false)
和isNaN(null)
返回都是true
,由於false
和null
都能被成功轉化爲數字0
, 因此對於isNaN
來講,它們是數字
最後咱們以表達式[[][[]]+[]][+[]][++[+[]][+[]]]
做爲文章的結尾
在這個表達式中出現了三種操做符,分別是
[]
+
+
++
根據操做符的優先次序表,咱們能肯定操做符的優先級依次是: []
> 一元操做符+
> ++
> +
因此根據優先級咱們首先能夠計算出表達式的+[]
部分,而且將表達式的這一部分用計算結果替換掉: [[][[]]+[]][0][++[0][0]]
接下來咱們把表達式拆分爲三部分看待: [ [][[]]+[] ] [0] [ ++[0][0] ]
。若是仍是不清晰的話,三部分從左到右分別是:
[ [][[]]+[] ]
[0]
[ ++[0][0] ]
咱們先看第一部分中+
前面的 [][[]]
操做數,第一個[]
是空數組,而緊跟着的[[]]
是屬性訪問器(成員操做符),屬性訪問器內的[]
會被強制轉化爲字符串類型,最終的結果便是空字符串''
,因此第一個操做數的最終結果實際上是[]['']
,便是undefined
,而又由於+
操做符的規則,最終[][[]]+[]
表達式的結果是字符串'undefined'
,那麼現階段表達式的結果是['undefined'][0][++[0][0]]
,即'undefined'[++[0][0]]
接下來咱們解決第三部分: [++[0][0]]
,我已經知道成員操做符[]
的優先級要高於自增操做符++
, 因此關於表達式++[0][0]
,咱們須要首先計算[0][0]
,結果是0
,以後計算++0
的結果便是1
因此最終表達式轉化爲了'undefined'[1]
,最終的結果便是'n'
本文也同時發佈在個人知乎專欄前端技術漫遊指南 上,歡迎你們關注