javascript中map不支持对象深度去重,因其键比较基于引用而非内容;实际是借助map缓存json字符串或组合键,配合自定义序列化或字段提取实现去重。

按完整对象内容去重(结构+值一致)
核心思路:把每个对象转成稳定、可比的字符串表示(如 JSON 字符串),再用 Map 记录是否已存在。
⚠️ 注意前提:对象必须是可安全 JSON 序列化的(不能含函数、undefined、Symbol、Date、RegExp、循环引用等);且属性顺序需一致,否则 `JSON.stringify({a:1,b:2})` 和 `JSON.stringify({b:2,a:1})` 结果不同,会被当成两个对象。
推荐写法:
- 用 Map 存储 JSON 字符串 → 原对象 映射,避免重复添加
- 遍历时先 stringify,查 Map 是否已有该字符串,没有就存入并推入结果
function deepUniqueByValue(arr) {
const seen = new Map();
const result = [];
for (const item of arr) {
const key = JSON.stringify(item);
if (!seen.has(key)) {
seen.set(key, item);
result.push(item);
}
}
return result;
}
const data = [
{ id: 1, name: "Alice", tags: ["js", "web"] },
{ id: 1, name: "Alice", tags: ["js", "web"] }, // 完全相同
{ id: 2, name: "Bob" }
];
console.log(deepUniqueByValue(data)); // 保留第一个,去重第二个
按多个指定字段组合去重(如 id + email)
这不是“深度”比对,但很常用——你希望当多个属性共同构成唯一标识时才视为重复。这时不需要 JSON,只需拼接或生成组合键。
优势:不依赖序列化,性能更好,兼容任意类型(只要字段可转字符串)。
- 提取多个字段,用固定分隔符拼成字符串键(如
item.id + '|' + item.email) - 或用数组转字符串:
JSON.stringify([item.id, item.email])(更稳妥,防分隔符冲突)
function uniqueByKeys(arr, keys) {
const seen = new Map();
return arr.filter(item => {
const key = JSON.stringify(keys.map(k => item[k]));
if (seen.has(key)) return false;
seen.set(key, true);
return true;
});
}
const users = [
{ id: 1, email: "a@x.com", role: "admin" },
{ id: 1, email: "a@x.com", role: "user" }, // id+email 相同 → 视为重复
{ id: 2, email: "b@y.com", role: "admin" }
];
console.log(uniqueByKeys(users, ['id', 'email']));
真正深度比对(含嵌套、函数、Date 等)
Map 仍不参与比对,但你可以引入轻量级深比较函数(如 Lodash 的 isEqual 或手写递归比对),再结合 Map 缓存已处理对象的引用或哈希值。
实际做法通常是:遍历 + 手动检查 acc.some(existing => isEqual(existing, current)),此时 Map 不是必需的,反而 reduce/filter + 自定义 isEqual 更直观。
- 若数据量小(filter + findIndex + isEqual 足够清晰
- 若数据量大且需高频去重,可预计算每个对象的稳定哈希(如用
object-hash库生成 hash 字符串),再用 Map 缓存 hash → 对象
为什么不直接用 Map 键存对象?
因为 map.set({a:1}, 'x') 和 map.get({a:1}) 一定返回 undefined —— 两次创建的对象引用不同。Map 的 has/get 判断的是键的引用相等性,不是内容相等性。
所以 Map 在这里只是高效缓存“判重依据”的容器(字符串、数字、Symbol),不是直接拿对象当键去重。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











