先记住这个答案
NaN 是 Number 类型里的一个特殊值,表示某次数值运算没有产生有意义的结果。比如 0 / 0、parseInt("abc")、Math.sqrt(-1) 都会返回 NaN。IEEE 754 标准规定 NaN 不等于任何值,包括它自己,所以 NaN === NaN 和 NaN == NaN 都是 false。可靠的检测方式是 Number.isNaN(),它只在参数确实是 NaN 时返回 true,不做隐式类型转换。
NaN === NaN为falseNumber.isNaN()不做类型转换,最可靠- 全局
isNaN()会先转数字,容易误判 includes能找到NaN
Number.isNaN 和全局 isNaN 的区别
JavaScript 有两个判断 NaN 的方法,名字很像但行为不同。全局 isNaN() 会先把参数转成数字,再判断转换结果是不是 NaN。这意味着 isNaN("hello") 返回 true——不是因为 "hello" 是 NaN,而是因为 Number("hello") 是 NaN。这个行为经常导致误判。
Number.isNaN() 是 ES6 加的,不做任何转换。参数不是 number 类型直接返回 false,只有参数确实是 NaN 这个值时才返回 true。
isNaN 的对比JavaScriptconsole.log(isNaN(NaN));
console.log(isNaN('hello'));
console.log(isNaN(undefined));
console.log(Number.isNaN(NaN));
console.log(Number.isNaN('hello'));
console.log(Number.isNaN(undefined));查看输出与解释
true
true
true
true
false
false前三行全局 isNaN 对 "hello" 和 undefined 都返回 true,因为它们被 Number() 转换后都是 NaN。后三行 Number.isNaN 只对真正的 NaN 返回 true。在实际代码中应该始终用 Number.isNaN。
数组查找和相等算法的关系
NaN 不等于自身这件事还会影响数组查找。indexOf 用的是严格相等 ===,找 NaN 永远返回 -1。includes 用的是 SameValueZero 算法,能识别 NaN。
indexOf 找不到 NaN 但 includes 能JavaScriptconsole.log([1, NaN, 3].indexOf(NaN));
console.log([1, NaN, 3].includes(NaN));
console.log(Object.is(NaN, NaN));查看输出与解释
-1
true
trueObject.is 用的是 SameValue 算法,也能识别 NaN。SameValue 和 SameValueZero 的唯一区别是对 +0 和 -0 的处理:SameValue 区分它们,SameValueZero 不区分。Map 和 Set 的键比较用的是 SameValueZero,所以 NaN 可以当 Map 的键,后续用另一个 NaN 也能命中同一个键。
NaN 还有一个特性叫传播性:任何包含 NaN 的算术运算,结果还是 NaN。NaN + 1 是 NaN,NaN > 0 是 false,NaN < 0 也是 false。这意味着一旦计算链里出现了 NaN,后面的结果全部失效。所以拿到 NaN 时不应该简单地替换成 0,而应该追溯是哪一步输入出了问题。
容易答错的地方
- 用
value === NaN做判断 - 这个表达式永远返回
false,不管value是什么。面试中写出if (x === NaN)会直接暴露对NaN特性不了解。应该用Number.isNaN(x)。 - 用全局
isNaN校验用户输入 isNaN("")返回false(因为Number("")是0,不是NaN),isNaN(" ")也返回false(纯空白转成0)。但isNaN("hello")返回true。这些行为混在一起很容易让校验逻辑出 bug。正确做法是先用Number()显式转换,再用Number.isNaN检查结果,或者用Number.isFinite做更严格的校验。
面试官还会怎么问?
为什么 typeof NaN 是 "number"?
NaN 是 IEEE 754 浮点数标准定义的一个特殊数值,用来表示数值运算失败的结果。它确实属于 Number 类型,不是一个独立的 JavaScript 类型。类型正确不代表值可用,就像 Infinity 也是 "number" 一样。
JSON.stringify 怎么处理 NaN?
输出 null。JSON.parse 后拿到的是 null,原来的 NaN 信息就丢了。如果需要在 JSON 里保留这个语义,得自己约定协议,比如用特定字符串表示。
NaN 可以当 Map 的键吗?
可以。Map 的键比较使用 SameValueZero 算法,能识别 NaN。map.set(NaN, 'value') 之后,map.get(NaN) 能拿到 "value",即使两个 NaN 在 === 下不相等。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。