JavaScript "相等" 的二三事

joy鈺發表於2017-12-19

相等不相等?

先來隨便舉幾個?吧~

'0' == true          //?
[1] == [1]           //?
[1] == 1             //?
null == false        //?
null == undefined    //?
NaN === NaN          //?
+0 === -0            //?
Object.is([], [])    //?
Object.is(-0, +0)    //?
Object.is(NaN, NaN)  //?

var arr = [NaN, 0, +0]
arr.indexOf(-0)      //?
arr.indexOf(NaN)     //?
arr.includes(-0)     //?
arr.includes(NaN)    //?

可能 ±0NaN 會糾結一點,還是比較基礎的,也許很多人一眼掃過去便知道答案了,網上也已經有了很多相關的經驗總結。我在這裡結合官方規範進行了整理,希望能帶給你不一樣的認識。

預備知識

ECMAScript Language Types

傳送門。根據最新規範,EcmaScript 一共有7種語言型別:

  • Undefined
  • Null
  • Number
  • String
  • Boolean
  • Symbol
  • Object

我們經常把 Object 型別稱為 引用資料型別其它5種則為基本資料型別。(Symbol怎麼說..?

ToNumber

傳送門。任意 EcmaScript 型別轉化為 Number 型別

型別 結果
Undefined NaN
Null +0
Boolean true -> 1,false -> +0
Number 不轉變
String 空字串 -> +0,有效的數字 -> 十進位制數字,其它 -> NaN
Object 先ToPrimitive(hint Number),再ToNumber
Symbol 拋錯,TypeError 錯誤

ToPrimitive

傳送門。內部方法,主要功能是將引用資料型別轉化為基本資料型別

  • 根據內部標記 hint 的不同有不同的呼叫順序。
  • hint有三種:defaultnumberstringdefault 預設遵照 number 規則。
  • default/number:先 valueOf,後 toString。一般轉化規則皆如此。
  • string:先 toString,後 valueOf。如Date物件方法、String()轉化等。
  • 如果 toString/valueOf 中某一方法返回型別不為物件型別,則直接返回該值,不會繼續呼叫後面方法。如果兩者都返回物件型別,會拋 TypeError 錯誤。

-0、+0、0 的疑惑

明明日常沒什麼卵用,為什麼會有±0?

  • 其實遵從IEEE754標準的程式語言都有±0的概念,IEEE754標準的64位浮點數,是以1+11+53形式的符號位+階數位+尾數位表示。
  • 符號位、階數位、尾數位都是0,那便是+0,也就是常規的數字0
  • 符號位為1,階數位、尾數位都是0,那便是 -0
  • IEEE754還規定了NaN無窮及其它的相應規範,有興趣可自行查詢相關資料。

PS

這部分其實是後加的,你會發現每個知識點都是緊密相連的,構成了一個龐大的知識網路,限於篇幅我不會詳細介紹,但我會盡量貼出規範出處,大家可自行研究。

SameValueNonNumber 內部方法

SameValueNonNumber 方法接收兩個引數 x 和 y ,其中 x 和 y 都不是 Number 型別,該方法返回 truefalse

主要規則

  1. 斷言:x 不是 Number 型別。
  2. 斷言:x 和 y 是 相同型別。
  3. 如果 x 是 Undefined 型別,返回 true
  4. 如果 x 是 Null 型別,返回 true
  5. 如果 x 是 String 型別:

    • 如果 x 和 y 長度相同且相應編碼單元相同,返回 true
    • 否則返回 false
  6. 如果 x 是 Boolean 型別:

    • 如果 x 和 y 都是true 或者 都是false,返回 true
    • 否則返回 false
  7. 如果 x 是 Symbol 型別:

    • 如果 x 和 y 都是相同 Symbol 值,返回 true
    • 否則返回 false
  8. 如果 x 和 y 指向同一物件,返回 true 。否則返回 false

小結

相同型別比較規則(除Number型別)

  1. 都是 undefined,相等
  2. 都是 null,相等
  3. String 型別中,都是相同字串,相等
  4. Boolean 型別中,都是 true 或者 都是 false,相等
  5. Symbol 型別中,都是相同 Symbol 值,相等
  6. Object 型別中,引用同一物件,相等

使用

哪些 JavaScript 公開方法採用了 SameValueNonNumber 比較呢?

  1. 公開方法木有
  2. 接著看下去你就會知道,撇開數值型別比較,SameValueNonNumberSameValueSameValueZero=== 的公共方法。

SameValueZero 內部方法

SameValueZero 方法接收兩個引數 x 和 y ,其中 x 和 y 是 EcmaScript 任意型別值,該方法返回 truefalse

主要規則

  1. 如果 x 和 y 的型別不同,返回 false
  2. 如果 x 是 Number 型別:

    • 如果 x 和 y 都是 NaN ,返回 true
    • 如果 x 是 -0 ,y 是 +0 ,返回 true
    • 如果 x 是 +0 ,y 是 -0 ,返回 true
    • 如果 x 和 y 數值相等,返回 true
    • 返回 false
  3. 返回 SameValueNonNumber(x, y) 方法的返回值。

小結

  1. 不同型別不相等
  2. Number 型別中:±0 相等。NaN 和 NaN 相等。其它相同數值相等
  3. SameValueNonNumber 比較:

    • 都是 undefined,相等
    • 都是 null,相等
    • String 型別中,都是相同字串,相等
    • Boolean 型別中,都是 true 或者 都是 false,相等
    • Symbol 型別中,都是相同 Symbol 值,相等
    • Object 型別中,引用同一物件,相等

使用

哪些 JavaScript 公開方法採用了 SameValueZero 比較呢?

  1. Array.prototype.includes
  2. Map.prototype.delete
  3. Map.prototype.has
  4. Map.prototype.set
  5. Set.prototype.delete
  6. Set.prototype.has
  7. Set.prototype.add
  8. ArrayBuffer 和 DataView 部分方法

SameValue 內部方法

SameValue 方法接收兩個引數 x 和 y ,其中 x 和 y 是 EcmaScript 中任意型別值,該方法返回 truefalse

主要規則

  1. 如果 x 和 y 的型別不同,返回 false
  2. 如果 x 是 Number 型別:

    • 如果 x 和 y 都是 NaN ,返回 true
    • 如果 x 是 -0 ,y 是 +0 ,返回 false
    • 如果 x 是 +0 ,y 是 -0 ,返回 false
    • 如果 x 和 y 數值相等,返回 true
    • 返回 false
  3. 返回 SameValueNonNumber(x, y) 方法的返回值。

小結

  1. 不同型別不相等
  2. Number 型別中:±0 不相等。NaN 和 NaN 相等。其它相同數值相等
  3. SameValueNonNumber 比較:

    • 都是 undefined,相等
    • 都是 null,相等
    • String 型別中,都是相同字串,相等
    • Boolean 型別中,都是 true 或者 都是 false,相等
    • Symbol 型別中,都是相同 Symbol 值,相等
    • Object 型別中,引用同一物件,相等

使用

哪些 JavaScript 公開方法採用了 SameValue 比較呢?

  1. Object.is
  2. 最新的 ES 規範 中,你會發現許多其它內部方法和公開方法都應用了 SameValue 比較方法,其中大部分也沒有涉及數值比較。
  3. 至於為什麼是 SameValue 方法,而不是 SameValueZero===。其實我也不知道。。。我個人傾向於認為:SameValue 方法原本在 ES5 規範中便存在了,最新的規範是為了保持規範一致而繼續沿用。

=== 嚴格相等運算

Strict Equality Comparison,x === y,返回 true 或者 false

主要規則

  1. 如果 x 和 y 的型別不同,返回 false
  2. 如果 x 是 Number 型別:

    • 如果 x 是 NaN ,返回 false
    • 如果 y 是 NaN ,返回 false
    • 如果 x 和 y 數值相等,返回 true
    • 如果 x 是 -0 ,y 是 +0 ,返回 true
    • 如果 x 是 +0 ,y 是 -0 ,返回 true
    • 返回 false
  3. 返回 SameValueNonNumber(x, y) 方法的返回值。

小結

  1. 不同型別不相等
  2. Number 型別中:±0 相等。NaN 和 NaN 不相等。其它相同數值相等
  3. SameValueNonNumber比較:

    • 都是 undefined,相等
    • 都是 null,相等
    • String 型別中,都是相同字串,相等
    • Boolean 型別中,都是 true 或者 都是 false,相等
    • Symbol 型別中,都是相同 Symbol 值,相等
    • Object 型別中,引用同一物件,相等

使用

哪些 JavaScript 公開方法採用了 === 比較呢?

  1. === 嚴格相等運算
  2. 左右兩邊是相同型別的 == 相等運算
  3. switch語句中的case
  4. Array.prototype.indexOf
  5. Array.prototype.lastIndexOf

== 相等運算

Abstract Equality Comparison,x == y,返回 true 或者 false

主要規則

  1. 如果 x 和 y 的型別相同:

    • 返回嚴格相等運算結果 x === y 。
  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 是 Number、String、Symbol 中任意一個型別 ,y 是 Object 型別,返回 x == ToPrimitive(y) 運算結果。
  9. 如果 y 是 Number、String、Symbol 中任意一個型別 ,x 是 Object 型別,返回 ToPrimitive(x) == y 運算結果。
  10. 返回 false

小結

  1. 相同型別:遵循 === 嚴格相等比較規則。
  2. null == undefined,相等
  3. 不同型別:

    • 基本資料型別轉換為 Number 型別再 == 比較。
    • 引用資料型別執行內部 ToPrimitive方法後再 == 比較。

使用

哪些 JavaScript 公開方法採用了 == 比較呢?

  1. 只有這隻 == 相等運算

相等不相等

開頭的答案。如果對結果感到好奇,不妨對著上面的過程比對~

'0' == true         // false
[1] == [1]          // false
[1] == 1            // true
null == false       // false
null == undefined   // true
NaN === NaN         // false
+0 === -0           // true
Object.is([], [])   // false
Object.is(-0, +0)   // false。見SameValue
Object.is(NaN, NaN) // true。見SameValue

var arr = [NaN, 0, +0]
arr.indexOf(-0)     // 1。見===
arr.indexOf(NaN)    // -1。見===
arr.includes(-0)    // true。見SameValueZero
arr.includes(NaN)   // true。見SameValueZero

總結

  1. SameValueZeroSameValue===這仨完全差不多嘛!

    • 相同點:

      - 不同型別即不相等。
      - 相同型別遵從`SameValueNonNumber`規則。
    • 不同點:對±0NaN 的判斷上各有不同。
  2. Array.prototype.includes 為代表的SameValueZero

    • ±0 相等
    • NaN 和 NaN 相等
  3. Object.is 為代表的SameValue

    • ±0 不相等
    • NaN 和 NaN 相等
  4. ===Array.prototype.indexOf 為代表的===

    • ±0 相等
    • NaN 和 NaN 不相等
  5. ==

    • 相同型別採用===嚴格比較。
    • 不同型別會隱式轉換:

      • 基本資料型別轉換為 Number 型別再 == 比較。
      • 引用資料型別執行 ToPrimitive 轉換後再 == 比較。
      • undefined/null 特例。

相關文章