Set.prototype.add() 判定重复依据 SameValueZero 算法:NaN 与 NaN 相等;+0、-0、0 三者互等;对象按引用判等。它不同于 === 和 Object.is(),仅在 +0/-0 相等性上与后者不同。

Set.prototype.add() 判定“重复”的依据,就是 SameValueZero 算法——它不是你日常用的 ===,也不是 Object.is(),而是一个有明确行为边界的底层比较规则。
SameValueZero 的核心判定规则
这个算法在 ECMAScript 规范中被明确定义,用于 Set(和 Map、Array.prototype.includes 等)的成员存在性检查。它的判断逻辑可归纳为三条:
-
NaN 和 NaN 被视为相等:哪怕
NaN === NaN是false,new Set([NaN, NaN])的 size 一定是1 -
+0、-0、0 三者互等:无论按什么顺序插入
+0、-0、0,Set 最终只保留一个;打印出来可能显示为0,但内部已合并 -
对象/函数/Symbol 按引用判等:两个字面量相同但内存地址不同的对象,如
{a: 1}和{a: 1},会被视为不同元素,add()全部接受
为什么不是 ===?也不是 Object.is()?
三者行为差异明显,直接用代码验证最直观:
-
+0 === -0→ true,但Object.is(+0, -0)→ false,而SameValueZero(+0, -0)→ true -
NaN === NaN→ false,Object.is(NaN, NaN)→ true,SameValueZero(NaN, NaN)→ true -
Object.is({},{})→ false,SameValueZero 同样是 false(因非同一引用),这与===一致
简言之:SameValueZero = Object.is() 的变体,唯一区别就是把 +0 和 -0 当作相等。
如何在实际中观察 add() 的判定效果
不靠文档,靠几行测试就能确认当前环境是否遵循该逻辑:
-
console.log(new Set([NaN, NaN]).size)→ 输出 1 -
console.log(new Set([+0, -0, 0]).size)→ 输出 1 -
console.log(new Set([{x:1}, {x:1}]).size)→ 输出 2 -
const a = {}; console.log(new Set([a, a]).size)→ 输出 1
这些结果稳定出现,就说明 add() 正在使用 SameValueZero。
业务中容易踩坑的点
真正影响开发的,往往不是理论,而是隐含语义冲突:
- 用
new Set(arr)去重数字数组时,-0可能被静默转成0,导致原始符号信息丢失 - 处理用户输入的数值时,若业务上区分
+0和-0(如金融方向标记),直接进 Set 会出错 - 对对象去重,不能依赖结构一致,必须先序列化或提取唯一 key,否则永远得不到预期结果
-
NaN虽然被合并,但它自身不可被===捕获,所以手动写filter((x, i) => arr.indexOf(x) === i)无法复现 Set 行为










