iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 26
0
Modern Web

前端開發 30 個問題系列 第 26

JavaScript abstract equality comparison

前言
2020 秋天,我將用 30 天的時間,來嘗試回答和網路前端開發相關的 30 個問題。30 天無法一網打盡浩瀚的前端知識,有些問題可能對有些讀者來說相對簡單,不過期待這趟旅程,能幫助自己、也幫助讀者打開不同的知識大門。有興趣的話,跟著我一起探索吧!

今天要來談談關於 JavaScript abstract equality comparison 的事情。我們都知道在 JavaScript 當中,在做比較的時候,有 ===== 之分,分別代表 abstract equality comparisonstrict equality comparison

strict equality comparison 會同時比較左右兩邊運算元的「型別」和「值」,如果只要有一個不同,那麼就會回傳 false。

譬如

1 === '1'      // false

但如果使用 abstract equality comparison 則就有不同的結果:

1 == '1'       // true

因為 JavaScript 會「很聰明」的幫我們轉型,把兩邊的運算元轉換成可以比較的型別與值。

不過,JavaScript 究竟是根據什麼規則進行轉換的呢?

Abstract Equality comparision

其實規則都寫在 ECMA 的規格文件 當中了:

今天,就讓我們一起好好看一下吧!

在進行 x == y 的比較時:

1
如果 x 和 y 的 type 都一樣,那麼其實就跟操作 strict equality comparision 一樣,不需要進行強制轉型。譬如

1 === 1                  // true
'hello' === 'world'     // false

2
如果 x 為 null 而 y 為 undefined,回傳 true

null == undefined       // true

3
如果 x 為 undefined 而 y 為 null,回傳 true

undefined == null       // true

4
如果 x 是 number 而 y 是 string,那麼會將 y 轉成 number 然後再進行比較

1 == '1'      // true
2 == '1'      // false

5
如果 x 是 string 而 y 是 number,那麼會將 x 轉成 number 然後再進行比較

'1' == 1      // true
'2' == 1      // false

6
如果 x 是 BigInt 而 y 是 string,那麼會將 y 轉成 BigInt 然後再進行比較。如果轉換的過程中出現 NaN,則直接回傳 false

1 == '1'      // true
2 == '1'      // false

7
如果 x 是 string 而 y 是 BigInt,則回傳 y == x 的結果(參考上面的規定)

'1' == 1      // true
'2' == 1      // false

8
如果 x 是一個 boolean 值,那麼會將 x 轉回 number 後再進行比較

true == 0      // false
true == 1      // true
true == 2      // false

如果這時候 y 是字串,那麼就會參考第四點來做比較

true == '0'      // false
true == '1'      // true
true == '2'      // false

9
如果 y 是一個 boolean 值,則回傳 y == x 的結果(參考上面的規定)

0 == true       // false
1 == true       // true
2 == true        // false
'0' == true      // false
'1' == true     // true
'2' == true     // false

10
如果 x 是 String, Number, BigInt, 或是 Symbol,而 y 是個 Object,那麼會需要先把 y 轉換成 Primative (ToPrimitive(y)) 之後再進行比較。

但是,ToPrimitive(y) 會變成什麼東西呢?

如果有特別設定 hint,那麼就會使用該 hint,譬如是numberstring。如果沒有設定 hint,就會使用 default

知道 hint 是什麼之後,接下來,就會開始進行轉換。在實作上,會使用 @@toPrimitive API 接口的方式,像下面這樣:

const obj = {
  [Symbol.toPrimitive](hint) {
    if (hint == 'number') {
      return 10;
    }
    if (hint == 'string') {
      return 'hello';
    }
    return true;
  }
};

根據 ECMA 文件說明,會先看看即將要轉換的 object,是否有轉換的 hint,也就是告訴大家該 object 該如何轉型。

如果根據 hint 而得到的轉換結果「不是 undefined」,本身不是 object,那麼就直接回傳結果。如果轉換結果還是 object,那麼就會丟出 TypeError。

hint 實際上來自於 "需要轉換環境",譬如以剛剛的 obj 來說

console.log(+obj)        // 10        -- hint is "number"
console.log(`${obj}`)    // "hello"   -- hint is "string"
console.log(obj + '')    // "true"    -- hint is "default"
Number(obj)              // 10        -- hint is "number"
String(obj)              // "hello"   -- hint is "string"

如果根據 hint 而得到的轉換結果「是 undefined」,那麼這時候就需要額外做以下的處理。

首先,如果原本的 hintdefault,那麼這時候就會被強制轉成 number。如果 hint

  • string,那麼就會去找這個 object 裡面是否有 toString() 或是 valueOf 的方法,之後依序提取。
  • number,那麼就會去找這個 object 裡面是否有 valueOf 或是 toString() 的方法,之後依序提取。

兩種狀況看起來很像,差別在於不同方法的提取順序。之後,就會「依照順序」呼叫方法,如果得到的結果不是 object,就會回傳結果;若還是 object,則會丟出 TypeError。

舉例來說,這裡我們使用原生的陣列 (沒有使用者定義的 @@toPrimitive):

let arr = [1]
console.log(+arr)        // 1        -- hint is "number"
console.log(`${arr}`)    // "1"      -- hint is "string"
Number(arr)              // 1        -- hint is "number"
String(arr)              // "1"      -- hint is "string"

比較特別的是

console.log(arr + '')    // "1"      -- hint is "default"

因為 hintdefault,所以會被自動轉為 number,接著,就會依序呼叫 valueOftoString 方法。結果分別為

[1].valueOf()       // [1]   object
[1].toString()      //  "1"  string

因此最後會回傳 string 的結果。

如果改成 let arr = [1, 2] 的話,最後就無法順利轉成 Primitive 囉!


11
如果 x 是 object 而 y 是 String, Number, BigInt, 或是 Symbol,那麼就會直接回傳 y == x 的結果(參考上面的規則)

12
如果 x 和 y 其中一個為 BigInt 另外一個為 numebr,只要其中有一個是 NaN, 無限大或無限小,直接回傳 false。如果沒有,則會比較實際上的數值大小,然後回傳結果

13
如果沒有符合上述的任何一個規則,直接回傳 false。譬如我們在規則 10 的時候,轉換過程中出現 TypeError (例外狀況),那麼就會在這裡直接回傳 false。譬如


End

閱讀原始碼或文件,就可以理解發明者是如何「設計」規則與工具。不管覺得有理還是無理,但這些規則基本上就是默默主宰著世界的運作。

我是覺得很有趣啦,你覺得呢?

Ref


TD
Be curious as astronomer, think as physicist, hack as engineer, fight as baseball player

More about me

"Life is like riding a bicycle. To keep your balance, you must keep moving."


上一篇
Event capturing and bubbling
下一篇
NaN, not a number
系列文
前端開發 30 個問題31

尚未有邦友留言

立即登入留言