JavaScript 奇怪事件簿

很差意思作了一回標題黨,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

  1. 操做符的使用方法和優先級
  2. 操做數在操做符的上下文中數據類型轉化規則
  3. 語法中的特例

+ 操做符

+在 JavaScript 中有三個做用:數組

  1. 鏈接字符串:var result = 'Hello' + 'World'
  2. 計算數字之和:var result = 1 + 2
  3. 做爲一元操做符:+variable

在表達式中+是操做符(operator),操做符操做的對象(上面例子中的HelloWorld12)名爲操做數(operand)瀏覽器

一元+操做符的運算規則是:ToNumber(ToPrimitive(operand)),也就是把任意類型都轉化爲數字類型。ide

當操做數的數據類型不一致時,會根據如下規則進行轉化:code

  • 若是至少一個操做數是對象數據類型(object),則須要將它轉化爲基礎類型(primitive),即字符串、數字或者布爾
    1. 若是對象是Date類型,那麼調用toString()方法
    2. 不然優先調用 valueOf() 方法
    3. 若是valueof()方法不存在或者並無返回一個基礎類型,那麼調用toString()
    4. 當數組轉化爲基礎類型時,JavaScript 會使用join(',')方法
    5. 單純的 Javascript 對象 {} 轉化的結果是 [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)。由於篇幅關係在這裏咱們簡單的針對題目聊聊後者

若是==操做符的操做數的數據類型不一樣:

  1. 若是一個操做數是null,而且另一個操做數是undefined,他們是相等的
  2. 若是一個操做數是數值類型,而且另外一個是字符串類型,那麼把字符串類型轉化爲數值類型再進行比較
  3. 若是一個操做數是布爾類型,那麼把true轉化爲1,false轉化爲0在進行比較
  4. 若是一個操做數是對象,另外一個操做數是數字或者字符串,那麼把對象轉化爲基本類型再進行比較
  • 根據以上規則,在計算表達式'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,由於falsenull都能被成功轉化爲數字0, 因此對於isNaN來講,它們是數字

結束

最後咱們以表達式[[][[]]+[]][+[]][++[+[]][+[]]]做爲文章的結尾

在這個表達式中出現了三種操做符,分別是

  • 成員操做符: []
  • 一元操做符: +
  • 做爲求和或者鏈接字符串做用的操做符: +
  • 自增操做符: ++

根據操做符的優先次序表,咱們能肯定操做符的優先級依次是: [] > 一元操做符+ > ++ > +

因此根據優先級咱們首先能夠計算出表達式的+[]部分,而且將表達式的這一部分用計算結果替換掉: [[][[]]+[]][0][++[0][0]]

接下來咱們把表達式拆分爲三部分看待: [ [][[]]+[] ] [0] [ ++[0][0] ]。若是仍是不清晰的話,三部分從左到右分別是:

  1. [ [][[]]+[] ]
  2. [0]
  3. [ ++[0][0] ]

咱們先看第一部分中+前面的 [][[]] 操做數,第一個[]是空數組,而緊跟着的[[]]是屬性訪問器(成員操做符),屬性訪問器內的[]會被強制轉化爲字符串類型,最終的結果便是空字符串'',因此第一個操做數的最終結果實際上是[][''],便是undefined,而又由於+操做符的規則,最終[][[]]+[]表達式的結果是字符串'undefined',那麼現階段表達式的結果是['undefined'][0][++[0][0]],即'undefined'[++[0][0]]

接下來咱們解決第三部分: [++[0][0]],我已經知道成員操做符[]的優先級要高於自增操做符++, 因此關於表達式++[0][0],咱們須要首先計算[0][0],結果是0,以後計算++0的結果便是1

因此最終表達式轉化爲了'undefined'[1],最終的結果便是'n'

本文也同時發佈在個人知乎專欄前端技術漫遊指南 上,歡迎你們關注

參考文章

相關文章
相關標籤/搜索