【3分鐘速覽】如何“嚴謹地”判斷兩個變數是否相同

AlienZHOU發表於2020-01-08

引言

如何“嚴謹地”判斷兩個變數相同?僅僅使用 === 就可以了麼?

【3分鐘速覽】如何“嚴謹地”判斷兩個變數是否相同

嚴格相等

我們可以非常快的寫一個 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 也能獲取到該值。

【3分鐘速覽】如何“嚴謹地”判斷兩個變數是否相同

於此對應的,也有個 Number.NEGATIVE_INFINITY 的值,實際就是 -Infinity

Infinity 比較特殊的一點在於,在 JavaScript 中 1 / Infinity-1 / Infinity。 被認為是相等的(由於 +0-0,下一節會進一步介紹)

【3分鐘速覽】如何“嚴謹地”判斷兩個變數是否相同

而在很多場景中,包括像一些 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

【3分鐘速覽】如何“嚴謹地”判斷兩個變數是否相同

而其倒數 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

它有一個特點 —— 自己不等於自己:

【3分鐘速覽】如何“嚴謹地”判斷兩個變數是否相同

這可能會導致判斷出現 I 類錯誤(棄真錯誤):原本是相同的,卻被我們判斷為不相同。

解決的方法也很簡單,JavaScript 中只有 NaN 會有“自己不等於自己”的特點。所以只需要判斷兩個變數是否都“自己不等於自己”即可,即都為 NaN

x !== x && y !== y
複製程式碼

如果兩個變數都為 NaN,那麼他們其實就還是相同的。

總結

總的來說,我們的加強版就是額外處理了 +0/-0NaN 的情況。

實際專案中,很多時候由於並不會碰這樣的業務值,或者這些邊界情況的判斷並不影響業務邏輯,所以使用 === 就足夠了。

而在一些開源庫中,由於需要更加嚴謹,所以很多時候就會考慮使用第三版的這類方法。例如在 react-redux 中對 props 和 state 前後相等性判斷underscore 中的相等判斷方法等。而 underscore 中更進一步還對 nullundefined 做了特殊處理。

相關文章