在 JS 裡比較是否相等時,到底要用兩個等於 ==
還是三個等於 ===
呢?==
跟 ===
到底差在哪?為什麼大家總是說盡量不要用 ==
呢?
最近剛好被學弟問到相關的問題,雖然知道 ==
背後再做什麼,但又覺得 JS spec 上寫得好長好難記,也就勸了學弟不用真的去記。但看完 Kyle Simpson 的解釋後,就默默記起來了。所以只能怪罪自己懶,因此寫下這個筆記。
Kyle Simpson 是著名的 You Don’t Know JS (YDKJS) 的作者。我感受到他費了不少力氣淬煉出讓大家都好消化的解釋。
Kyle 甚至提出應該多多使用 ==
,然後用 ===
來警告讀這段 code 的人說,「你要注意這裡,因為我搞不清楚等號兩邊的型別(type)是什麼」。
筆記目錄:
TL;DR 結論
先說結論,我還是會:
- 只有當不知道比較的兩方型別是什麼,才要擔心
==
或是===
的問題 ==
允許型別轉換(type coercion),===
則不允許- 還是繼續使用
===
- 比較
null
跟undefined
會用==
會繼續使用 ===
是因為大家也都這樣習慣了,而且用起來也沒有太多問題。我自己看起來 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:
- If Type(x) is the same as Type(y), then
- 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:
- If Type(x) is different from Type(y), return false.
- If Type(x) is Number or BigInt, then
a. Return ! Type(x)::equal(x, y). - Return ! SameValueNonNumeric(x, y).
首先,===
做的事情是當發現兩個型別不同時,就不做其他事了,直接回傳 false
。
ES2020 第二點的寫法相比之前是不一樣的,這是因為 BigInt
是 ES2020 的新成員。我們就只先看 Number
,所以再次找到 Spec 裡 Number::equal ( x, y ) 的部分:
- If x is NaN, return false.
- If y is NaN, return false.
- If x is the same Number value as y, return true.
- If x is +0 and y is -0, return true.
- If x is -0 and y is +0, return true.
- Return false.
看到這裡,我們也知道為什麼 NaN === NaN
是 false
了。對 BigInt
有興趣的人兒可以看 BigInt::equal ( x, y )。
第三點就不是本篇重點了,有興趣的人可以看 SameValueNonNumeric ( x, y )。
整個流程裡,===
不會進行任何型別轉換(type coercion)。
兩個等於 ==
Loose Equals
接下來就是恐怖的 ==
了,Spec 看起來很長,不過 Kyle 總結完就相當容易懂了:
- 如果 type 相同 =>
===
null
orundefined
=> equal- 偏好:Number
- 非 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
當不是 null
、undefined
或 number
時,能轉成 number
就盡量轉,然後再做 ==
。
4. 非 primitive 型別 => ToPrimitive
先簡單說 primitive type 有 6 個:undefined
、string
、number
、boolean
、symbol
和 bigint
。
ToPrimitive
和上面的 ToNumber
以後有機會再寫筆記來記錄。(老高上身)
總之就是把非 primitive type 的一直轉到變成這 6 種型別再進行 ==
,這時就再看看前面幾點所提到的行為,一切就通了。
如果能夠看到這裡,可愛筆記由衷感謝你。
至於,如果疑惑結論在哪,請回到可愛的抬頭:TL;DR 結論。
Comments