内容来自: You-dont-know-JS中类型与文法的第二章(值)
不是数字的数字
如果你不使用同为 number
(或者可以被翻译为十进制或十六进制的普通 number
的值)的两个操作数进行任何算数操作,那么操作的结果将失败而产生一个不合法的 number
,在这种情况下你将得到 NaN
值。
NaN
在字面上代表“不是一个 number
(Not a Number)”,但是正如我们即将看到的,这种文字描述十分失败而且容易误导人。将 NaN
考虑为“不合法数字”,“失败的数字”,甚至是“坏掉的数字”都要比“不是一个数字”准确得多。
举例来说:
var a = 2 / "foo"; // NaN
typeof a === "number"; // true
换句话说:“‘不是一个数字’的类型是‘数字’”!为这使人糊涂的名字和语义欢呼吧。
NaN
是一种“哨兵值”(一个被赋予了特殊意义的普通的值),它代表 number
集合内的一种特殊的错误情况。这种错误情况实质上是:“我试着进行数学操作但是失败了,而这就是失败的 number
结果。”
那么,如果你有一个值存在某个变量中,而且你想要测试它是否是这个特殊的失败数字 NaN
,你也许认为你可以直接将它与 NaN
本身比较,就像你能对其它的值做的那样,比如 null
和 undefined
。不是这样。
var a = 2 / "foo";
a == NaN; // false
a === NaN; // false
NaN
是一个非常特殊的值,它从来不会等于另一个 NaN
值(也就是,它从来不等于它自己)。实际上,它是唯一一个不具有反射性的值(没有恒等性 x === x
)。所以,NaN !== NaN
。有点奇怪,对吧?
那么,如果不能与 NaN
进行比较(因为这种比较将总是失败),我们该如何测试它呢?
var a = 2 / "foo";
isNaN( a ); // true
够简单的吧?我们使用称为 isNaN(..)
的内建全局工具,它告诉我们这个值是否是 NaN
。问题解决了!
别高兴得太早。
isNaN(..)
工具有一个重大缺陷。它似乎过于按照字面的意思(“不是一个数字”)去理解 NaN
的含义了 —— 它的工作基本上是:“测试这个传进来的东西是否不是一个 number
或者是一个 number
”。但这不是十分准确。
var a = 2 / "foo";
var b = "foo";
a; // NaN
b; // "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true -- 噢!
很明显,"foo"
根本 不是一个 number
,但它也绝不是一个 NaN
值!这个 bug 从最开始的时候就存在于 JS 中了(存在超过了十九年的坑)。
在 ES6 中,终于提供了一个替代它的工具:Number.isNaN(..)
。有一个简单的填补,可以让你即使是在前 ES6 的浏览器中安全地检查 NaN
值:
if (!Number.isNaN) {
Number.isNaN = function(n) {
return (
typeof n === "number" &&
window.isNaN( n )
);
};
}
var a = 2 / "foo";
var b = "foo";
Number.isNaN( a ); // true
Number.isNaN( b ); // false -- 咻!
实际上,通过利用 NaN
与它自己不相等这个特殊的事实,我们可以更简单地实现Number.isNaN(..)
的填补。在整个语言中 NaN
是唯一一个这样的值;其他的值都总是 等于它自己。
所以:
if (!Number.isNaN) {
Number.isNaN = function(n) {
return n !== n;
};
}
怪吧?但是好用!
不管有意还是无意,在许多真实世界的 JS 程序中 NaN
可能是一个现实的问题。使用 Number.isNaN(..)
(或者它的填补)这样的可靠测试来正确地识别它们是一个非常好的主意。
如果你正在程序中仅使用 isNaN(..)
,悲惨的现实是你的程序 有 bug,即便是你还没有被它咬到!