js中爲何你不敢用 「==」

前言

類型轉換在各個語言中都存在,而在 JavaScript 中因爲缺少對其的瞭解而不慎在使用中常常形成bug被人詬病。爲了不某些場景下的意外,甚至推崇直接使用 Strict Equality( === )來代替 ==。這確實能避免不少bug,但更是一種對語言不理解的逃避(我的觀點)。數組

引入

先拋出在 You Don’t Know JavaScript (中) 看到的一個例子安全

[] == [] // false
  [] == ![] // true
  {} == !{} // false
  {} == {} // false

是否是很奇怪?本文將從書中看到的知識與規範相結合,來詳細說明一下JavaScript在類型轉換時候發生的故事。函數

類型轉換

不少人喜歡說顯示類型轉換與隱式類型轉換,但我的感受只是說法上的不一樣,實質都在發生了類型轉換而已,故不想去區分他們了(感受一萬我的有一萬種說法)spa

僅在6大基本類型 null undefined number boolean string object 做討論 symbol未考慮code

 

  • 舉個栗子

    var a = String(1)
    var b = Number('1')
    var c = 1 + ''
    var d = +'1'

    a,b直接調用了原生函數,發生了類型轉換。c,d使用了+運算符的一些規則,發生了類型轉換。這些是很簡單的也是咱們經常使用的。對象

    其實真正起做用的,是語言內部對規範中抽象操做的實現,接下來咱們所說的 ToString, ToNumber, ToBoolean等都是抽象操做,而不是JS裏對應的內置函數ip

  • ToString – 規範9.8

    按照如下規則轉化被傳遞的參數字符串

    Argument Type Result
    Undefined 「undefined」
    Null 「null」
    Boolean true -> 「true」
    false – > 「false」
    Number NaN -> 「NaN」
    +0 -0 -> 「0」
    -1 -> 「-1」
    infinity -> 「Infinity」
    較大的數科學計數法 (詳見規範9.8.1)
    String 不轉換 直接返回
    Object 1. 調用ToPrimitive抽象操做, hint 爲 String 將返回值做爲 value
    2. 返回ToString(value)
    String(undefined) // "undefined"
    String(null) // "null"
    String(true) // "true"

    ToPrimitive 抽象操做下面會說起string

  • ToNumber – 規範9.3

    按照如下規則轉換被傳遞參數it

    Argument Type Result
    Undefined NaN
    Null +0
    Boolean true -> 1
    false -> +0
    Number 直接返回
    String 若是不是一個字符串型數字,則返回NaN(具體規則見規範9.3.1)
    Object 1. 調用ToPrimitive抽象操做, hint 爲 Number 將返回值做爲 value
    2. 返回ToNumber(value)
  • ToBoolean – 規範9.2

    按照如下規則轉換被傳遞參數

    Argument Type Result
    Undefined false
    Null false
    Boolean 直接返回
    Number +0 -0 NaN -> false
    其餘爲true
    String 空字符串(length爲0) -> false
    其餘爲true
    Object true
  • ToPrimitive – 規範9.1

    顧名思義,該抽象操做定義了該如何將值轉爲基礎類型(非對象),接受2個參數,第一個必填的要轉換的值,第二個爲可選的hint,暗示被轉換的類型。

    按照如下規則轉換被傳遞參數

    Argument Type Result
    Undefined 直接返回
    Null 直接返回
    Boolean 直接返回
    Number 直接返回
    String 直接返回
    Object 返回一個對象的默認值。一個對象的默認值是經過調用該對象的內部方法[[DefaultValue]]來獲取的,同時傳遞可選參數hint。
  • [[DefaultValue]] (hint) – 規範8.12.8

    • 當傳遞的hint爲 String 時候,
      • 若是該對象的toString方法可用則調用toString
        • 若是toString返回了一個原始值(除了object的基礎類型)val,則返回val
      • 若是該對象的valueOf方法可用則調用valueOf方法
        • 若是valueOf返回了一個原始值(除了object的基礎類型)val,則返回val
      • 拋出TypeError的異常
    • 當傳遞的hint爲 Number 時候,
      • 若是該對象的valueOf方法可用則調用valueOf方法
        • 若是valueOf返回了一個原始值(除了object的基礎類型)val,則返回val
      • 若是該對象的toString方法可用則調用toString
        • 若是toString返回了一個原始值(除了object的基礎類型)val,則返回val
      • 拋出TypeError的異常
    • hint的默認值爲Number,除了Date object
    • 舉個栗子
    var a = {}
    a.toString = function () {return 1}
    a.valueOf = function () {return 2}
    String(a) // "1"
    Number(a) // 2
    a + '' // "2"   ???????
    +a // 2
    a.toString = null
    String(a) // "2"
    a.valueOf = null
    String(a) // Uncaught TypeError: balabala

彷佛咱們發現了一個很不合規範的返回值,爲何 a + ''不該該返回」1″嗎

  • 問題的答案其實很簡單 + 操做符會對兩遍的值進行 toPrimitive 操做。因爲沒有傳遞 hint 參數,那麼就會先調用a.valueOf 獲得2後由於+右邊是字符串,因此再對2進行ToString抽象操做後與」」的字符串拼接。

不要畏懼使用 ==

基礎概念已經瞭解了,那麼在 == 中到底發生了什麼樣的類型轉換,而致使了常常產生出乎意料的bug,致使了它臭名昭著。

  • 抽象相等 – 規範11.9.3

    x == y 判斷規則以下:

    1. 若是xy類型相同 (與嚴格相等判斷一致,不贅述了,詳見規範)
    2. 若是 x 爲 null y 爲 undefined, 返回true
    3. 若是 x 爲 undefined y 爲 null, 返回true
    4. 若是 x 類型爲 Number, y 類型爲 String, 返回 x == ToNumber(y)
    5. 若是 x 類型爲 String, y 類型爲 Number, 返回ToNumber(x) == y
    6. 若是 x 類型爲 Boolean, 返回 ToNumber(x) == y
    7. 若是 y 類型爲 Boolean, 返回 x == ToNumber(y)
    8. 若是 x 類型爲 String 或 Number, y 類型爲 Object, 返回 x == ToPrimitive(y)
    9. 若是 x 類型爲 Object, y 類型爲 String 或 Number, 返回 ToPrimitive(x) == y
    10. return false
  • 再看引入

[] == [] // false
  // 1. 兩遍類型都爲 Object,比較引用地址,不一樣返回false 搞定
  [] == ![] // true
  // 1. ![]強制類型轉換 變爲 [] == false
  // 2. 根據規範第7條,返回 [] == ToNumber(false), 即 [] == 0
  // 3. 根據規範第9條,返回ToPromitive([]) == 0,數組的valueOf爲自己,不是原始值,則返回toString()即 "" == 0
  // 4. 根據規範第5條,返回ToNumber("") == 0, 即 0 == 0
  // 5. 根據規範第1條,返回 true

  // 下面的不贅述了,分析相似上面
  {} == !{} // false
  {} == {} // false

咱們不難看出如下幾點

  • 其實在x y類型相同的時候,== 與 === 沒有任何區別。
  • 除了undefined與null, 大多數值都會轉換爲相同類型後進行對比,也就是說 === 是 == 某些狀況下必經的步驟

引用 << 你不知道的JS(中) >> 中的2句話

  • 若是兩遍的值中有 true 或者 false , 千萬不要使用 == (會被轉爲數字0,1來進行判斷,會出現一些意外的狀況)
  • 若是兩遍的值中有[]、」」或者0,儘可能不要使用 ==

抽象比較

先來看看這個例子

var a = { b: 42 }
var b = { b: 43 }
a < b // false
a == b // false
a > b // false

a <= b // true
a >= b // true

是否是感受到世界又崩塌了???

讓咱們來仔細分析一下

var a = { b: 42 }
var b = { b: 43 }
a < b // false 
// 1. 兩遍調用ToPrimitive, 返回[object Object] 兩遍一致 返回 false
a == b // false
// 兩遍不一樣的引用,返回false
a > b // false
// 同 a < b

a <= b // true
// 按規範實際上是處理成 !(a > b) 因此爲true
a >= b // true

因此在不相等比較的時候,咱們最後仍是進行手動的類型轉換較爲安全

總結

深刻了解類型轉換的規則,咱們就能夠很容易取其精華去其糟粕,寫出更安全也更簡潔可讀的代碼

相關文章
相關標籤/搜索