Set去重最简且安全,天然支持null和NaN(SameValueZero算法使NaN唯一);filter+indexOf不可靠(NaN识别失败);reduce+Object.is最严谨,可区分NaN、-0/+0等。

JavaScript 数组去重时,NaN 和 null 是两个容易被忽略的特殊值:NaN 不等于自身(NaN === NaN 为 false),而 null 虽然全等比较正常(null === null 为 true),但在某些去重方法(如用 Set)中会被正确保留;但若混用类型转换或宽松比较,就可能出错。
用 Set 去重(推荐,天然支持 null,需单独处理 NaN)
Set 构造器能自动去重,且对 null 和 undefined 都能准确区分和保留。但它对 NaN 的处理是“友好”的——Set 内部使用 SameValueZero 算法,把所有 NaN 视为相等,因此多个 NaN 只会保留一个,这反而是符合预期的行为。
✅ 正确示例:
const arr = [1, NaN, 'a', null, undefined, NaN, null]; const unique = [...new Set(arr)]; // → [1, NaN, 'a', null, undefined]
⚠️ 注意:这个结果是正确的 —— NaN 去重了,null 也保留了,没有误判。
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
用 filter + indexOf(不适用于 NaN)
indexOf 内部使用严格相等(SameValue)比较,但 不包含对 NaN 的特殊处理(ES5 中 indexOf 用的是 SameValueZero,实际现代引擎已修复,但兼容性仍建议避免依赖)。更关键的是:[NaN].indexOf(NaN) === -1(在部分旧环境或手动模拟逻辑中成立),导致 filter 无法识别重复的 NaN,全部保留。
❌ 错误写法(不可靠):
[NaN, NaN].filter((v, i, a) => a.indexOf(v) === i); // → [NaN, NaN]
✅ 替代方案(手动补 NaN 逻辑):
- 遍历数组,用
Array.prototype.includes()判断(includes使用 SameValueZero,能正确识别NaN) - 或用
findIndex+Object.is()(最严谨,可同时处理NaN、-0、+0)
用 reduce + Object.is(最严谨,推荐用于自定义逻辑)
Object.is(a, b) 是 ES6 引入的精确相等判断,它认为 NaN === NaN 且 -0 !== +0,比 === 更可靠。
✅ 示例(完整支持 NaN、null、undefined、-0、+0):
const arr = [1, NaN, null, undefined, '1', -0, +0, NaN, null];
const unique = arr.reduce((acc, cur) => {
const exists = acc.some(item => Object.is(item, cur));
return exists ? acc : [...acc, cur];
}, []);
// → [1, NaN, null, undefined, '1', -0, +0]
注意事项与常见误区
-
Set是最简方案,对NaN和null都安全,无需额外处理 —— 直接用[...new Set(arr)] - 避免用
JSON.stringify或类型转换(如String(x))去重,String(null) === 'null',但String(NaN) === 'NaN',看似可行实则丢失原始类型,且无法区分1和'1' -
Map或对象键去重时,null会被转成字符串'null',NaN同样转成'NaN',导致null和NaN与其他值冲突(例如{[null]: 1, [NaN]: 2}实际只有{'null': 1, 'NaN': 2})

















