JS 不是数字的数字

323 阅读2分钟

如果数学运算的操作数不是数字类型(或者无法解析为常规的十进制或十六进制数字), 就无法返回一个有效的数字,这种情况下返回值为 NaN

NaN 意指“不是一个数字”(not a number),这个名字容易引起误会,后面将会提到。将它 理解为“无效数值”“失败数值”或者“坏数值”可能更准确些。

例如

var a = 2 / "foo"; // NaN
typeof a === "number"; // true

换句话说,“不是数字的数字”仍然是数字类型。这种说法可能有点绕。

NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误 情况,即“执行数学运算没有成功,这是失败后返回的结果”。

有人也许认为如果要检查变量的值是否为NaN,可以直接和NaN 进行比较,就像比较 null 和 undefined 那样。实则不然。

var a = 2 / "foo";
a == NaN; // false
a === NaN; // false

NaN是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不 成立)的值。而 NaN != NaN 为 true,很奇怪吧?

既然我们无法对 NaN 进行比较(结果永远为 false),那应该怎样来判断它呢?

var a = 2 / "foo";
isNaN( a ); // true

很简单,可以使用内建的全局工具函数 isNaN(..) 来判断一个值是否是 NaN。

然而操作起来并非这么容易。isNaN(..) 有一个严重的缺陷,它的检查方式过于死板,就 是“检查参数是否不是 NaN,也不是数字”。但是这样做的结果并不太准确:

var a = 2 / "foo";
var b = "foo";
a; // NaN
b; "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true——晕!

很明显 "foo" 不是一个数字,但是它也不是 NaN。这个 bug 自 JavaScript 问世以来就一直存 在,至今已超过 19 年。

从 ES6 开始我们可以使用工具函数 Number.isNaN(..)。

ES6 之前的浏览器的 polyfill 如下:

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 不等于自身这个特点。NaN 是 JavaScript 中唯 一一个不等于自身的值。

于是我们可以这样:

if (!Number.isNaN) {
 Number.isNaN = function(n) {
 return n !== n;
 };
}

很多 JavaScript 程序都可能存在 NaN 方面的问题,所以我们应该尽量使用 Number.isNaN(..) 这样可靠的方法,无论是系统内置还是 polyfill。 如果你仍在代码中使用 isNaN(..),那么你的程序迟早会出现 bug。

来自‘你不知道的JavaScript’