引言
如何“嚴謹地”判斷兩個變數相同?僅僅使用 ===
就可以了麼?
嚴格相等
我們可以非常快的寫一個 is
方法來判斷變數 x 是否就是 y:
// 第一版
function is(x, y) {
return x == y;
}
複製程式碼
當然,你會很快發現,方法裡用了 ==
,由於隱式轉換的問題,這並不嚴謹。所以我們自然會使用如下的方法:
// 第二版
function is(x, y) {
return x === y;
}
複製程式碼
那麼這是否完美了呢?
一個“更嚴謹”的方法
// 第三版
function is(x, y) {
if (x === y) {
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
return x !== x && y !== y;
}
}
複製程式碼
上面方法相較於我們常用的第二版更復雜了。那麼為什麼多了這麼多判斷呢?
下面讓我們來詳細看看。
1. Infinity
瞭解 JavaScript 的同學應該會記得,在全域性中有一個叫做 Infinity
的屬性,表示數值上的無窮大。
Infinity 屬性的屬性特性 | |
---|---|
writable | false |
enumerable | false |
configurable | false |
同時,你用 Number.POSITIVE_INFINITY
也能獲取到該值。
於此對應的,也有個 Number.NEGATIVE_INFINITY
的值,實際就是 -Infinity
。
而 Infinity
比較特殊的一點在於,在 JavaScript 中 1 / Infinity
與 -1 / Infinity
。 被認為是相等的(由於 +0
和 -0
,下一節會進一步介紹)
而在很多場景中,包括像一些 deepEqual 之類的方法中,我們不希望將其判定為相等。學過統計的同學都知道假設檢驗中有兩類錯誤:
- I類錯誤:棄真錯誤(false positive)
- II類錯誤:取偽錯誤(false negative)
結合我們上面提到的,第一個條件判斷可能就會犯II類錯誤 —— 1 / Infinity
與 -1 / Infinity
不相同,卻判斷為相同了。所以需要進一步判斷:
x !== 0 || y !== 0 || 1 / x === 1 / y
複製程式碼
1 / Infinity
與 -1 / Infinity
在與 0
的相等判斷中都會為 true
而其倒數 Infinity
與 -Infinity
是不相等的,所以避免了 1 / Infinity
與 -1 / Infinity
的判斷問題。
2. +0
與 -0
其實,上面 Infinity
問題的核心原因在於於 JavaScript 中存在 +0
與 -0
。
我們知道每個數字都有其對應的二進位制編碼形式,因此 +0
與 -0
編碼是有區別的,平時我們不主動宣告的話,所使用的其實都是 +0
,而 JavaScript 為了我們的運算能更加方便,也做了很多額外工作。
想要更進一步瞭解
+0
與-0
可以讀一下 JavaScript’s two zeros 這篇文章。
但在很多判斷相等的工作上,我們還是會把 +0
與 -0
區分開。
x !== 0 || y !== 0 || 1 / x === 1 / y
複製程式碼
上面這個式子也就起到了這個作用。
3. NaN
JavaScript 中還有一個叫 NaN
全域性屬性,用來表示不是一個數字(Not-A-Number)
NaN 屬性的屬性特性 | |
---|---|
writable | false |
enumerable | false |
configurable | false |
它有一個特點 —— 自己不等於自己:
這可能會導致判斷出現 I 類錯誤(棄真錯誤):原本是相同的,卻被我們判斷為不相同。
解決的方法也很簡單,JavaScript 中只有 NaN
會有“自己不等於自己”的特點。所以只需要判斷兩個變數是否都“自己不等於自己”即可,即都為 NaN
:
x !== x && y !== y
複製程式碼
如果兩個變數都為 NaN
,那麼他們其實就還是相同的。
總結
總的來說,我們的加強版就是額外處理了 +0
/-0
與 NaN
的情況。
實際專案中,很多時候由於並不會碰這樣的業務值,或者這些邊界情況的判斷並不影響業務邏輯,所以使用 ===
就足夠了。
而在一些開源庫中,由於需要更加嚴謹,所以很多時候就會考慮使用第三版的這類方法。例如在 react-redux 中對 props 和 state 前後相等性判斷,underscore 中的相等判斷方法等。而 underscore 中更進一步還對 null
與 undefined
做了特殊處理。