javascript数组去重遇循环引用时,须用weakmap记录已见对象引用以避免无限递归;非对象类型用object.is()比较,对象则递归比对自有可枚举属性。

JavaScript 数组去重时遇到带循环引用的对象,不能直接用 JSON.stringify 或普通深比较,否则会抛出 TypeError: Converting circular structure to JSON。核心思路是:**在遍历过程中记录已见过的对象引用,避免重复访问和无限递归**。
用 WeakMap 跟踪对象引用(推荐)
WeakMap 可以以对象为键,且不阻止垃圾回收,天然适合追踪引用关系。配合递归遍历实现安全的“深度相等”判断:
- 对每个元素,先检查是否是对象且非 null;若是,用 WeakMap 查是否已处理过该引用——若已存在,说明是循环引用,直接返回 true 表示“视为相同”
- 若未见过,存入 WeakMap 并继续递归比对属性值(只比自有可枚举属性)
- 非对象类型(string/number/boolean/null/undefined/Symbol/BigInt)直接用
Object.is()比较
示例(简化版去重函数):
function uniqueByDeepEqual(arr) {
const seen = new WeakMap();
function equals(a, b) {
if (a === b) return true;
if (a == null || b == null || typeof a !== 'object' || typeof b !== 'object') {
return Object.is(a, b);
}
if (seen.has(a)) return seen.get(a) === b;
seen.set(a, b);
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (const key of keysA) {
if (!keysB.includes(key) || !equals(a[key], b[key])) return false;
}
return true;
}
return arr.filter((item, i) =>
arr.slice(0, i).every(prev => !equals(prev, item))
);
}
// 测试循环引用
const obj = { name: 'a' };
obj.self = obj;
const arr = [{ name: 'a', self: obj }, obj, { name: 'b' }];
console.log(uniqueByDeepEqual(arr)); // [{ name: 'a', self: obj }, { name: 'b' }]
用 Set + 自定义 key 生成(轻量替代)
如果对象结构相对固定、可预测,可以为每个对象生成唯一标识符(如路径哈希或序列化摘要),跳过循环部分:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 遍历对象时维护一个“路径栈”,遇到已见对象引用就跳过其子树(标记为
[Circular]) - 用
JSON.stringify(obj, (key, val) => seen.has(val) ? '[Circular]' : val)配合 replacer 和 WeakSet 记录已见对象 - 将生成的字符串作为 key 存入 Set,实现去重
注意:此法不保证 100% 语义相等(如属性顺序不同会导致 key 不同),但对多数业务场景足够稳定。
避免深比较:改用引用去重或 ID 标识
真正需要“深去重”的场景其实较少。更健壮的做法是:
- 前端数据尽量带唯一 ID(如
id字段),直接按id去重:arr.filter((item, i, a) => a.findIndex(v => v.id === item.id) === i) - 若必须按内容,且对象来自后端,可在服务端生成 content-hash 并透传到前端
- 纯前端状态管理中,优先用 Immer 或不可变更新,配合引用一致性减少深层对比需求
不建议的方式(踩坑提醒)
以下方法在循环引用下会失败或产生错误结果:
-
JSON.stringify()直接调用 → 抛错 - 用
Map或普通Object存对象作 key → 自动转为"[object Object]",全部冲突 - 递归函数无引用缓存 → 栈溢出或无限循环
- 仅比
toString()或constructor→ 完全忽略内容差异
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










