JavaScript 可愛筆記 #0 – 兩個等於真的有那麼壞嗎?

在 JS 裡比較是否相等時,到底要用兩個等於 == 還是三個等於 === 呢?===== 到底差在哪?為什麼大家總是說盡量不要用 == 呢?

最近剛好被學弟問到相關的問題,雖然知道 == 背後再做什麼,但又覺得 JS spec 上寫得好長好難記,也就勸了學弟不用真的去記。但看完 Kyle Simpson 的解釋後,就默默記起來了。所以只能怪罪自己懶,因此寫下這個筆記。

Kyle Simpson 是著名的 You Don’t Know JS (YDKJS) 的作者。我感受到他費了不少力氣淬煉出讓大家都好消化的解釋。

Kyle 甚至提出應該多多使用 ==,然後用 === 來警告讀這段 code 的人說,「你要注意這裡,因為我搞不清楚等號兩邊的型別(type)是什麼」。

筆記目錄:

TL;DR 結論

先說結論,我還是會:

  1. 只有當不知道比較的兩方型別是什麼,才要擔心 == 或是 === 的問題
  2. == 允許型別轉換(type coercion),=== 則不允許
  3. 還是繼續使用 ===
  4. 比較 nullundefined 會用 ==

會繼續使用 === 是因為大家也都這樣習慣了,而且用起來也沒有太多問題。我自己看起來 Kyle 也不是真的堅持大家要改用 ==,而是要大家知道自己到底在寫些什麼。

有問題的通常都是 code,而不是用兩個等於(==)還是三個等於(===)。

兩個等於真的這麼壞嗎

或多或少都曾聽說 == 之所以壞壞,是因為當型別(type)不同時,他會自動幫我們做轉換。回過頭來,我們知道要比較的兩方型別是什麼嗎?

要被比較是否相等的兩方,型別本來就應該要盡量一致。若眼前的 code 讓你完全無法判斷它是什麼型別的時候,比起掙扎到底是要用 =====,應該要先搞清楚這段 code 到底在做什麼。或許要考慮的是是否重構(refactor)或是幫這段 code 寫個註解,避免禍害延續。

隨便舉幾個 == 為人詬病的例子:

console.log(0 == '') // true
console.log(0 == '     ') // true
console.log(28 == [28]) // true

乍看之下好像真的很邪惡,但仔細想想,為什麼會有這種奇怪的比較發生才是問題所在吧。

型別 (type) 相同時

首先,先來探討當 type 相同時===== 有什麼差別。曾經有個都市傳說這樣說的:

==先轉換成相同型別再比較,=== 先判斷型別再比較值。

都市傳說

那型別相同時會發生什麼事呢?

關於兩個等於 ==Spec 是這樣說的:

The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:

  1. If Type(x) is the same as Type(y), then
    1. Return the result of performing Strict Equality Comparison x === y.

由此可知,== 第一件事就是檢查型別,並不是直接進行轉換!

型別相同時,== 就是在做 ===

三個等於 === Strict Equals

關於三個等於 ===Spec 是這樣說的:

The comparison x === y, where x and y are values, produces true or false. Such a comparison is performed as follows:

  1. If Type(x) is different from Type(y), return false.
  2. If Type(x) is Number or BigInt, then
    a. Return ! Type(x)::equal(xy).
  3. Return ! SameValueNonNumeric(xy).

首先,=== 做的事情是當發現兩個型別不同時,就不做其他事了,直接回傳 false

ES2020 第二點的寫法相比之前是不一樣的,這是因為 BigInt 是 ES2020 的新成員。我們就只先看 Number,所以再次找到 Spec 裡 Number::equal ( xy ) 的部分:

  1. If x is NaN, return false.
  2. If y is NaN, return false.
  3. If x is the same Number value as y, return true.
  4. If x is +0 and y is -0, return true.
  5. If x is -0 and y is +0, return true.
  6. Return false.

看到這裡,我們也知道為什麼 NaN === NaNfalse 了。對 BigInt 有興趣的人兒可以看 BigInt::equal ( xy )

第三點就不是本篇重點了,有興趣的人可以看 SameValueNonNumeric ( xy )

整個流程裡,=== 不會進行任何型別轉換(type coercion)

兩個等於 == Loose Equals

接下來就是恐怖的 == 了,Spec 看起來很長,不過 Kyle 總結完就相當容易懂了:

  1. 如果 type 相同 => ===
  2. null or undefined => equal
  3. 偏好:Number
  4. 非 primitive 型別 => ToPrimitive

1. 如果 type 相同 => ===

在「型別 (type) 相同時」探討過了,就是做 ===

2. null or undefined => equal

重點如下:

if (x === null || x === undefined) {
  // code
}
// 跟上面結果一樣,但又更簡潔
if (x == null) {
  // code
}

這裡是我覺得 == 會讓 code 看起來更簡潔的作法。選 null 是因為相比 undefined 可以少打幾個字。我就懶!

3. 偏好:Number

Abstract Equality Comparison spec
約一半的篇幅都在轉型成 number

當不是 nullundefinednumber 時,能轉成 number 就盡量轉,然後再做 ==

4. 非 primitive 型別 => ToPrimitive

先簡單說 primitive type 有 6 個:undefinedstringnumberbooleansymbolbigint

ToPrimitive 和上面的 ToNumber 以後有機會再寫筆記來記錄。(老高上身)

總之就是把非 primitive type 的一直轉到變成這 6 種型別再進行 ==,這時就再看看前面幾點所提到的行為,一切就通了。

如果能夠看到這裡,可愛筆記由衷感謝你。
至於,如果疑惑結論在哪,請回到可愛的抬頭:TL;DR 結論

Reference

Add comment

這個網站採用 Akismet 服務減少垃圾留言。進一步瞭解 Akismet 如何處理網站訪客的留言資料

Reader Favorites

Recent Posts

Categories