在 JavaScript 数组中包含 NaN 时,includes 能够正确检测出 NaN 并返回 true,而 indexOf 无法检测到并返回 -1。本文深入讲解两者的底层比较算法差异、规范细节、完整代码示例与替代方案。
JS数组中包含NaN时,includes和indexOf哪个能检测到
时间:2026-11-05
浏览:119
文章目录
假设你正在处理一份由表单计算或数据接口返回的数值列表,由于某个数据计算溢出或转换失败,数组中混入了特殊数值 NaN(例如 [10, NaN, 30])。当需要写一段条件语句判断该数组是否包含了 NaN 时,如果随意在 includes 和 indexOf 之间二选一,就可能会写出始终无法命中的隐蔽 Bug。
直接给出核心结论:Array.prototype.includes 能够成功检测到 NaN 并返回 true,而 Array.prototype.indexOf 无法检测到 NaN,无论数组中是否存在该值,始终返回 -1。
一、直观示例:代码执行与输出对比
在浏览器控制台或 Node.js 环境中执行以下代码,可以直接观察两者的返回差异:
// 声明一个包含 NaN 的数值数组
const list = [10, NaN, 30];
// 1. 使用 includes 检测
const includesResult = list.includes(NaN);
console.log("list.includes(NaN) 的结果:", includesResult);
// 示例输出:
// list.includes(NaN) 的结果: true
// 2. 使用 indexOf 检测
const indexOfResult = list.indexOf(NaN);
console.log("list.indexOf(NaN) 的结果:", indexOfResult);
// 示例输出:
// list.indexOf(NaN) 的结果: -1
从输出中可以看到,list.includes(NaN) 顺利返回了 true,准确反映了数组中存在 NaN;然而 list.indexOf(NaN) 返回了 -1,在字面语义上表示“未找到该元素”。即使我们将 NaN 显式放在数组的第一个位置(索引 0),indexOf(NaN) 依旧会返回 -1。
arr.indexOf(item) !== -1 来实现“包含判断”。如果目标元素可能是 NaN,这种写法将永远得出 false 的错误结论。
二、原因剖析:为什么 indexOf 找不到 NaN?
这一差异的根源并不在于数组本身,而在于 ECMAScript 规范为两个方法定义的底层比较算法不同。
Array.prototype.indexOf 是在 ECMAScript 5 (ES5) 中标准化的老牌方法。根据 ECMA-262 规范,indexOf 在比对数组元素与传入目标时,使用的是严格相等比较算法(Strict Equality Comparison),即我们在日常代码中使用的 === 运算符。
在 IEEE 754 浮点数算术标准以及 JavaScript 语言规范中,NaN(Not-a-Number)具有一个极其特殊的性质:NaN 与任何值都不相等,包括它自己:
console.log(NaN === NaN); // 输出:false
console.log(Object.is(NaN, NaN)); // 输出:true (采用 SameValue 算法)
当 indexOf 遍历数组并移动到存储了 NaN 的索引位置时,它在内部执行比对表达式 arr[k] === target。此时由于 NaN === NaN 的结果为 false,引擎判定当前位置不匹配,并继续向后查找;遍历完所有项后依然没有匹配项,最终按照规范返回 -1。
三、includes 的内部机制:SameValueZero 算法
为了弥补 indexOf 语义模糊(既用来查位置又用来查存在性)以及无法正确识别 NaN 的缺陷,ECMAScript 2016 (ES7) 正式引入了 Array.prototype.includes。
规范中明确规定,includes 方法在遍历比对元素时,不再使用严格相等 ===,而是使用名为 SameValueZero 的比较算法。该算法的比较步骤如下:
- 如果两个被比较值的类型不同,直接返回
false。 - 如果被比较值都是
Number类型:- 如果两个值都是
NaN,则返回true; - 如果一个是
+0另一个是-0,则返回true; - 其他情况下,数值相同返回
true,不同返回false。
- 如果两个值都是
- 对于其他基本类型或对象引用,遵循常规的同值判断规则。
正是由于 SameValueZero 算法对 NaN 做出了“视为等价”的明确规定,arr.includes(NaN) 在遍历到数组内的 NaN 时,比对结果为 true,从而立刻终止遍历并返回 true。
四、进阶场景:如果需要获取 NaN 的索引位置该怎么办?
既然 indexOf(NaN) 无法找到索引,而 includes(NaN) 只返回布尔值,那么在实际项目中如果需要拿到 NaN 所在的位置下标,应该如何处理?
正确的解决方案是使用 ES6 引入的 Array.prototype.findIndex 配合 Number.isNaN:
const list = [10, NaN, 30];
// 使用 findIndex 传递精准判断谓词
const targetIndex = list.findIndex(item => Number.isNaN(item));
console.log("NaN 所在的索引下标:", targetIndex);
// 示例输出:
// NaN 所在的索引下标: 1
Number.isNaN 而非全局 isNaN全局函数
isNaN(value) 在判断前会先尝试将参数隐式转换为数值,比如 isNaN("abc") 也会返回 true。而 Number.isNaN(value) 严格限定“类型必须是 Number 且值是 NaN”,不会触发隐式转换,能够杜绝误判。
五、各方法比对总结与选型指南
为了在实际开发中更加清晰地选择合适的方法,下表汇总了 JavaScript 数组常用查找方法针对 NaN 的检测能力与适用场景:
| 方法名称 | 引入标准 | 底层比较机制 | 能检测到 NaN 吗 | 典型适用场景 |
|---|---|---|---|---|
Array.prototype.includes |
ES2016 (ES7) | SameValueZero | ✔ 能检测 (返回 true) | 判断数组是否包含特定元素(首选) |
Array.prototype.indexOf |
ES5 | 严格相等 (===) | ✘ 不能检测 (恒返回 -1) | 查找普通基础数据类型的下标位置 |
Array.prototype.findIndex |
ES2015 (ES6) | 自定义回调函数 | ✔ 能检测 (配合 Number.isNaN) | 需要获取 NaN 或复杂对象的下标 |
Array.prototype.some |
ES5 | 自定义回调函数 | ✔ 能检测 (配合 Number.isNaN) | 多条件校验是否存在满足项 |
总的来说,当遇到数组中包含 NaN 时:只要判断是否存在,直接使用 arr.includes(NaN);如果需要获取位置索引,使用 arr.findIndex(Number.isNaN);坚决避免使用 arr.indexOf(NaN) 进行任何有效性判断。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。