你可能不知道的 NaN 以及 underscore 1.8.3 _.isNaN 的一個 BUG

這篇文章并不在我的 underscore 源碼解讀計劃中,直到 @pod4g 同學回復了我的 issue(詳見 https://github.com/hanzichi/underscore-analysis/issues/2#issuecomment-227361035)。其實之前也有同學提出 isNaN 有 native 的 function,正好借此文辨析下幾個常見的概念、方法,她們是 NaN,Number.NaN,isNaN,Number.isNaN,以及 underscore 中的 _.isNaN,順便揪出了一個 BUG。

順便安利,完整的 underscore 源碼解讀系列文章請戳 https://github.com/hanzichi/underscore-analysis

NaN & Number.NaN

ok,首先來了解下 NaN 和 Number.NaN 兩個屬性。

全局屬性 NaN 表示 Not-A-Number 的值,顧名思義,就是表示 不是一個數字

在編碼中很少直接使用到 NaN。通常都是在計算失敗時,作為 Math 的某個方法的返回值出現的(例如:Math.sqrt(-1))或者嘗試將一個字符串解析成數字但失敗了的時候(例如:parseInt("blabla"))。這樣做的好處是,不會拋出錯誤,只需要在下一步的運算中判斷上個步驟的運算結果是否是 NaN 即可。

接著來看 Number.NaN,這貨和 NaN 完全一樣。其實,歸根結底這倆貨都是屬于 Number 類型:

Object.prototype.toString.call(NaN)
// "[object Number]"
Object.prototype.toString.call(Number.NaN)
// "[object Number]"

isNaN & Number.isNaN

接著來聊 isNaN 和 Number.isNaN 倆方法。

我們都知道,雖然 NaN 作為 Number 類型,但是她不等于她自己, NaN == NaN 或者 NaN === NaN 都會返回 false,那么怎么檢測一個 NaN 值呢?答案大家都知道了,isNaN 方法。

isNaN(NaN)
// true
isNaN(undefined)
// true
isNaN({})
// true
isNaN("abc")
// true

好多東西傳入 isNaN 的結果都是 true,并不只是 NaN,為什么?因為參數會先被強制轉換成 Number 類型,然后再進行判斷。

Number(NaN)
// NaN
Number(undefined)
// NaN
Number({})
// NaN
Number("abc")
// NaN

ok,強制轉換后其實都變成了 NaN。

那么 Number.isNaN 和 isNaN 有何區別呢?和全局函數 isNaN() 相比,該方法不會強制將參數轉換成數字,只有在參數是真正的數字類型,且值為 NaN 的時候才會返回 true。

isNaN = function(value) {
    Number.isNaN(Number(value));
}

Number.isNaN = Number.isNaN || function(value) {
    return typeof value === "number" && isNaN(value);
}

值得注意的是,Number.isNaN 是 ES6 引入的,可以用上面的 Polyfill。

_.isNaN

最后來看看 underscore 對于 _.isNaN 的實現。

寫代碼首先得看需求,我們先看看 _.isNaN 的作用,查閱 API 文檔 http://underscorejs.org/#isNaN

this is not the same as the native isNaN function, which will also return true for many other not-number values, such as undefined.

文檔指出,_.isNaN 和 native 的 isNaN 并不一樣,必須是個 Number 類型(才可能返回 true),等等,似乎和 Number.isNaN 一樣?且慢下結論。

我們來看看 edge 版本對其的實現(https://github.com/jashkenas/underscore/blob/master/underscore.js):

// Is the given value `NaN`?
_.isNaN = function(obj) {
  return _.isNumber(obj) && isNaN(obj);
};

obj 得是個 Number 類型,并且能通過 isNaN 函數的判斷,才能返回 true。其實能通過這個函數的,只有兩個值,NaN 和 new Number(NaN)(當然還有 Number.NaN,前面說了,NaN 和 Number.NaN 是一樣的東西,下同)。

而能通過 Number.isNaN 函數的只有 NaN。(Number.isNaN(new Number(NaN) 會返回 false)

但是我看的 1.8.3 其實是這樣實現的:

_.isNaN = function(obj) {
  return _.isNumber(obj) && obj !== +obj;
};

其實這是有 BUG 的,很顯然 new Number(0) 并不應該是 Not-A-Number。

_.isNaN(new Number(0));
// true

為什么會這樣寫?這引發了我的好奇,找了下歷史記錄,是為了修復這個 issue https://github.com/jashkenas/underscore/issues/749。該 issue 認為,_.isNaN(new Number(NaN)) 應該返回 true。

我們可以看下再之前的版本對于 _.isNaN 的實現(https://github.com/jashkenas/underscore/commit/6ebb43f9b3ba88cc0cca712383534619b82f7e9b):

_.isNaN = function(obj) {      
   return obj !== obj;   
};

我又翻了下當時的測試數據(https://github.com/jashkenas/underscore/blob/6ebb43f9b3ba88cc0cca712383534619b82f7e9b/test/objects.js),發現當時沒有類似 new Number(0) 的測試數據(現在已經有了)。

總結

對于 NaN 的判斷,如果只針對 Number 類型,用 underscore 最新版的 _.isNaN 判斷完全沒有問題,或者用 ES6 的 Number.isNaN,兩者的區別就在于一個 new Number(NaN),不過話又說回來,沒人會這么蛋疼去這樣 new 一個 NaN 吧?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容