Set 对 NaN 的去重不是 bug,而是因使用 SameValueZero 算法:它令 NaN 与 NaN、+0 与 -0 均相等,但仅适用于原始值,包装对象不享受该规则。

JavaScript 中 NaN 在 Set 去重时看似矛盾:它不等于自身(NaN === NaN 为 false),却能在 Set 中自动去重。这不是 bug,而是规范明确设计的行为——关键在于 Set 不用 ===,而用 SameValueZero 算法。
SameValueZero 是核心规则
Set 和 Map 内部判断“是否重复”,依赖 SameValueZero 比较逻辑,它和 === 几乎一致,但有两个例外:
-
NaN 与 NaN 被视为相等:所以
new Set([NaN, NaN])长度是 1; -
+0 与 -0 被视为相等:所以
new Set([0, -0])也只存一个值(通常是0)。
常见误判场景
很多去重方法会在这里翻车,因为它们没用 SameValueZero:
-
arr.indexOf(NaN)返回-1,永远找不到 NaN —— 它用的是===; -
arr.includes(NaN)反而能命中,因为它内部用的就是 SameValueZero; -
Array.from(new Set(arr))能正确合并 NaN,但arr.filter((v, i) => arr.indexOf(v) === i)会保留所有 NaN。
包装对象不享受特殊待遇
SameValueZero 只对原始值生效。一旦你用了包装对象,规则立刻失效:
立即学习“Java免费学习笔记(深入)”;
-
new Set([NaN, new Number(NaN)])含两个元素 —— 原始 NaN ≠ Number 实例; -
new Set([NaN, Object(NaN)])同样保留两个; - 哪怕
Number(NaN)转成原始值再进 Set,也要显式调用.valueOf()或参与解构。
如何验证 NaN 是否被真正去重
别靠直觉,动手测最可靠:
-
const s = new Set([NaN]); s.add(NaN); console.log(s.size)→ 输出1; -
const m = new Map(); m.set(NaN, 'x'); m.has(NaN)→ 返回true; -
new Set([0, -0, NaN, NaN]).size→ 输出2(0/-0 合并为 1 个,NaN 合并为 1 个)。


















