這篇文章并不在我的 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 吧?