Set 使用 SameValueZero 算法判断重复,将 +0、-0、0 视为相等,NaN 只保留一个,不依赖 === 也不完全等同 Object.is(),导致去重行为与直觉不符。
Set 不是靠 === 判断两个值是否重复,而是用更底层的 SameValueZero 算法。这个算法和 Object.is() 几乎一样,但有个关键区别:它把 +0 和 -0 当作相等,而 Object.is(+0, -0) 返回 false。
这是最常让人困惑的点:虽然 NaN === NaN 是 false,Object.is(NaN, NaN) 是 true,而 Set 选择的是“只留第一个 NaN”——它不依赖 ===,也不完全等同于 Object.is(),而是按规范硬编码了 NaN 的唯一性。
new Set([NaN, NaN, NaN]) 的 .size 是 1
NaN === NaN 始终为 false,所以不能靠 === 模拟 Set 行为filter() + includes()),默认不会合并 NaN哪怕你显式写出 -0,Set 也会把它和 +0 当作同一个值:
new Set([+0, -0, 0]) // → Set { 0 }
注意:-0 被打印为 0,但它在内存中仍是 -0;只是 Set 内部判定时认为三者等价。
0、+0、-0 插入任意顺序,最终只保留一个Array.from(new Set([+0, -0]))[0] === -0 可能为 true(取决于插入顺序和引擎实现)JSON.stringify(-0) 或 String(-0) 做唯一性预处理,它们都输出 "0",会掩盖差异当你用 [...new Set(arr)] 去重数组,这些行为会悄悄影响结果:
["0", 0, false] → 三个不同值(字符串、数字、布尔),不会被合并[NaN, "NaN"] → 两个不同值(类型不同),都会保留[0, -0, Object.is(0, -0)] → false,但 Set 里仍只留一个new Set([{a:1}, {a:1}]) 长度是 2
真正需要稳定去重时,别直接塞原始数组进 Set——先统一转成字符串或提取 key,否则 +0/-0 和 NaN 的隐式合并可能破坏业务语义。