從 Number.isNaN 與 isNaN 的區別說起

再見尼克發表於2017-10-31

例子

大家先看一看下面這個例子,

isNaN(NaN);

isNaN('A String');

isNaN(undefined);

isNaN({});

Number.isNaN(NaN);

Number.isNaN('A String');

Number.isNaN(undefined);

Number.isNaN({});複製程式碼

如果你能很清楚答案,那麼這篇文章你可以略過。
不清楚的朋友,我們來慢慢來分析。

答案如下:

isNaN(NaN); // true

isNaN('A String'); // true

isNaN(undefined); // true

isNaN({}); // true

Number.isNaN(NaN); // true

Number.isNaN('A String'); // false

Number.isNaN(undefined); // false

Number.isNaN({}); // false複製程式碼

為什麼看起來同樣的函式,得出的結果為什麼不同呢?

NaN 是什麼?

在解釋 NaN 之前,我想先解釋下 type/value/variable 這個幾個容易混淆的概念,已經很清楚的朋友可以跳過這一小節

type / value / variable 是什麼

在 JavaScript 中,value一共有七種type

  1. null
  2. undefined
  3. boolean
  4. number
  5. string
  6. object
  7. symbol (ES6新增)

那麼,variable是什麼呢?就是我們平時 var 之後的宣告的那個東西。

type, value, variable 之間的關係可以這麼說:variable是存放value的容器,而value是有著type概念的,但是容器variable是沒有type的概念的,舉個例子


var a = 'foo';複製程式碼

容器 variable a 裝著 value 'foo', value 'foo' 的type是string

NaN

MDN裡面這麼描述

The global NaN property is a value representing Not-A-Number.

意思是是說:NaN是一個放在 global(瀏覽器裡是window)物件裡的一個value,是一個代表Not-A-Number的value.

意思還是很含糊。

那麼我們在看神書《You Don't Know JS》裡的描述

NaN literally stands for "not a number", though this label/description is very poor and misleading, It would be much more accurate to think of NaN as being "invalid number," "failed number," or even "bad number," than to think of it as "not a number."

根據上一個小結的知識,我們知道了,NaN是一個 value, 這個 value 的 type 是 number。

但是跟普通的type是number的value不一樣的是,NaN 代表 'Not a number' 這一意義。

那麼問題來了,怎麼判斷一個 value 是不是 NaN 呢?

isNaN()

也許有人會說,判斷還不容易嗎?直接比較不就好了。

NaN === NaN // false複製程式碼

NaN 跟它自己比較會返回false。

所以,我們就需要一個特殊的函式來判斷一個value是不是NaN了。

isNaN() 就橫空出世了。

我們再回頭看一看上面的例子

isNaN(NaN); // true複製程式碼

OK, 成功了,看似很完美,但是接著看以下例子

isNaN('A String'); // true

isNaN(undefined); // true

isNaN({}); // true複製程式碼

會發現,很明顯不是 NaN 的 value 也被誤判成 NaN 了。

這個BUG已經存在了20年,從JavaScript最開始就一直存在。很明顯當初的設計者,在設計isNaN()的時候,侷限了在 "Not a Number" 這一字面意思上了:只要不是number就會返回 true。

於是 ES6 為了彌補這一BUG(而不是修正,因為isNaN存在時間太長,有可能很多功能都是基於這個BUG之上的)引入了 Number.isNaN().

Number.isNaN(NaN); // true

Number.isNaN('A String'); // false

Number.isNaN(undefined); // false

Number.isNaN({}); // false複製程式碼

回頭看上面的例子,就明白了修復了什麼問題。

Number.isNaN() 的 polyfill

沒有ES6的情況下,可以採用以下polyfill

if (!Number.isNaN) {
  Number.isNaN = function(n) {
    return (
      typeof n === "number" &&
      window.isNaN( n )
    );
  };
}複製程式碼

簡單來看,就是在原有 isNaN() 的基礎上增加了一個 type 的判斷,因為 NaN 的 type 是 number。

還有一種更加簡單的實現

if (!Number.isNaN) {
    Number.isNaN = function(n) {
        return n !== n;
    };
}複製程式碼

利用了只有 NaN 不跟自己相等的特性。

順便吐槽一下MDN的解釋,他是這麼解釋 isNaN()

You could think of isNaN as:

var isNaN = function(value) {
    return Number.isNaN(Number(value));
}複製程式碼

他是在ES6新函式Number.isNaN()的基礎上,去解釋舊函式isNaN()的。

不過我們可以通過以上方式來解釋判斷 isNaN() 為什麼會出現

isNaN('A String'); // true

isNaN(undefined); // true

isNaN({}); // true複製程式碼

這樣的情況了。

作者部落格

作者微博

相關文章