JavaScript 的相等性
August 12, 2022在〈弱型別的代價 – WAT!〉中談過,JavaScript 中的相等性比較,主要有 ==
與 ===
,為了避免自動型態轉換來搗蛋,建議使用 ===
進行嚴格的相等比較。
在〈[與眾不同的資料型態](/basics/type/〉中談過 NaN
,這是個麻煩的傢伙,NaN === NaN
會是 false
,只能使用 isNaN
函式來判定一個值是不是 NaN
,偏偏 isNaN
又不是那麼可靠。在 ES6 以後有了 Number.isNaN
,在〈增強的數值與字串〉看過,現在只有 NaN
能讓 Number.isNaN
結果為 true
了。
然而,現在必須得問的是,在談到兩個值的相等性時,到底要像 ===
將 NaN
視為 false
,還是要像 Number.isNaN
(或者 isNaN
),將 NaN
視為 true
呢?
SameValueZero
例如,陣列中如果有 NaN
,你知道 indexOf
、lastIndexOf
找得到還是會傳回 -1?在 case
比對時,如果 case NaN
,那麼這會是個有效案例嗎?如果一個 Set
包含了 NaN
,那麼再 add
一個 NaN
,到底要接受還是不接受呢?
陣列的 indexOf
、lastIndexOf
採用的是 ===
,因此試圖找 NaN
的話,會傳回 false
,case
也是使用 ===
比較,因此 case NaN
是比對不到的:
> switch(NaN) {
... case NaN:
... console.log('NaN'); break;
... default:
... console.log('default');
... }
default
undefined
>
然而,Set
、Map
在比較相等性時,若有兩個 NaN
要比對,會視為相等,除此之外,行為跟 ===
相同,在 ES6 以後,稱這種相等演算為 SameValueZero,以有別於單純地使用 ===
時的相等運算。
SameValue
好吧!反正就是 NaN
的問題嘛!多記一種就是了,是這樣的嗎?來看看 Object.defineProperty
的行為:
> let o = {};
undefined
> Object.defineProperty(o, 'READONLY_N',
... { value: -0, writable: false, configurable: false, enumerable: false });
{}
> Object.defineProperty(o, 'READONLY_N',
... { value: 100, writable: false, configurable: false, enumerable: false })
;
TypeError: Cannot redefine property: READONLY_N
at Function.defineProperty (<anonymous>)
at repl:1:8
at ContextifyScript.Script.runInThisContext (vm.js:50:33)
at REPLServer.defaultEval (repl.js:240:29)
at bound (domain.js:301:14)
at REPLServer.runBound [as eval] (domain.js:314:12)
at REPLServer.onLine (repl.js:441:10)
at emitOne (events.js:121:20)
at REPLServer.emit (events.js:211:7)
at REPLServer.Interface._onLine (readline.js:282:10)
> Object.defineProperty(o, 'READONLY_N',
... { value: -0, writable: false, configurable: false, enumerable: false });
{}
>
一個特性如果被 Object.defineProperty
定義為唯讀,那麼就不能改變該特性的值,否則發生 TypeError
,然而,如果 Object.defineProperty
時實際上並沒有變動值,像上例那樣,還是指定為 -0
的話,並不會有 TypeError
,那麼,將之定義為 0 呢?
> Object.defineProperty(o, 'READONLY_N',
... { value: 0, writable: false, configurable: false, enumerable: false });
TypeError: Cannot redefine property: READONLY_N
at Function.defineProperty (<anonymous>)
at repl:1:8
at ContextifyScript.Script.runInThisContext (vm.js:50:33)
at REPLServer.defaultEval (repl.js:240:29)
at bound (domain.js:301:14)
at REPLServer.runBound [as eval] (domain.js:314:12)
at REPLServer.onLine (repl.js:441:10)
at emitOne (events.js:121:20)
at REPLServer.emit (events.js:211:7)
at REPLServer.Interface._onLine (readline.js:282:10)
>
驚喜!在這種情況下,0 與 -0 被視為不相等的喔!SameValueZero 演算將 0 與 -0 視為相等,而將 0 與 -0 視為不相等的演算,稱為 SameValue,這其實在 ES5 時就規範了,Object.is
採用的就是 SameValue 演算,因此 Object.is(0, -0)
的結果會是 false
:
> Object.is(0, -0);
false
>
SameValue 演算會將 NaN
視為相等,然而 0 與 -0 視為不相等,而 ES6 以後的 SameValueZero 會將 NaN
視為相等,0 與 -0 也視為相等。
總之,JavaScript 有四種相等性比較耶!好歡樂 !@#$%^ … XD
基本上,大多數情況,還是使用 ===
就好了,然而,如果要面對 NaN
、0
、-0
的時候,最好是搞清楚自己打算使用哪種相等性,而使用 API 時,也查清楚它會使用哪種相等性。