set对对象去重只看内存引用而非内容,故{a:1}和{a:1}被视为两个元素;只有同一引用如const a={a:1}; new set([a,a])才去重;json.stringify或自定义键可实现按值去重。

Set 在 JavaScript 中对对象(包括数组、对象字面量等)的“不重复”判断,只看内存引用,不看内容结构。所谓“字面量不重复”,其实是个常见误解——两个一模一样的对象字面量,比如 {a: 1} 和 {a: 1},在 Set 中是被当作两个不同元素的,因为它们是独立创建、互无关联的引用。
为什么对象字面量总被当作“新元素”?
JavaScript 的 Set 使用 SameValueZero 算法做成员判定,对对象类型只比较引用地址。每次写 {a: 1} 都会在堆中分配一块新内存,生成一个全新对象实例。
-
new Set([{a: 1}, {a: 1}])→ 包含两个元素,不是去重后的 1 个 -
const a = {a: 1}; new Set([a, a])→ 才是真正去重,只保留 1 个(因为是同一引用)
“字面量不重复”的真实含义
这里说的“不重复”,不是指 Set 主动识别并合并相同结构的字面量,而是指:你手动写的每个字面量表达式,都必然产生新引用,Set 忠实地照单全收,不会擅自认为“长得一样就该算一个”。它没有“深度比较”能力,也不解析结构,更不序列化。
- Set 不会把
[1,2]和[1,2]当作相同数组 - 也不会把
{x: 1, y: 2}和{y: 2, x: 1}当作相同对象(属性顺序不同、引用不同) - 甚至连
Object.freeze({a: 1})和{a: 1}也是两个不同引用
如何绕过这个限制实现“按值去重”?
想让 Set 表现出“按内容去重”,必须把对象转化为可比的唯一标识,常用方式有:
-
JSON 序列化:适用于纯数据对象(无函数、undefined、Date、RegExp、循环引用)
const seen = new Set(); const key = JSON.stringify(obj); if (!seen.has(key)) { seen.add(key); /* 处理 obj */ } -
自定义键生成器:对特定结构提取稳定字符串键,例如
`${user.id}-${user.name}` -
Map + 自定义比较:用 Map 存储,插入前遍历已存项,手动调用
deepEqual判断
Java 的 Set 与此有何本质不同?
Java 的 HashSet 虽也依赖引用,但允许你通过重写 hashCode() 和 equals(),让逻辑相等的对象返回相同哈希值并互相认定为“相等”。而 JavaScript 的 Set 没有提供类似钩子——它不调用你对象上的任何方法,只做引用判等。这是语言设计层面的差异,不是实现疏漏。










