在〈弱型別的代價 – WAT!〉中談過,JavaScript 中的相等性比較,主要有 == 與 ===,為了避免自動型態轉換來搗蛋,建議使用 === 進行嚴格的相等比較。
在〈與眾不同的資料型態〉中談過 NaN,這是個麻煩的傢伙,NaN === NaN 會是 false,只能使用 isNaN 函式來判定一個值是不是 NaN,偏偏 isNaN 又不是那麼可靠。在 ES6 中有了 Number.isNaN,在〈增強的數值與字串〉看過,現在只有 NaN 能讓 Number.isNaN 結果為 true 了。
然而,現在必須得問的是,在談到兩個值的相等性時,到底要像 === 將 NaN 視為 false,還是要像 Number.isNaN(或者 isNaN),將 NaN 視為 true 呢?
例如,陣列中如果有 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,以有別於單純地使用 === 時的相等運算。
好吧!反正就是 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 時,也查清楚它會使用哪種相等性。

