约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证
90% 的去重需求一行 [...new Set(arr)] 就够;剩下 10% 是对象数组,Set 在这里永远「去不掉」,原因和修复见下文。
const nums = [1, 2, 2, 3, 'a', 'a', null, null]; const unique = [...new Set(nums)]; console.log(unique); // [1, 2, 3, 'a', null] —— NaN 也能去重
Set 内部按 SameValueZero 比较,对对象来说就是比引用。两个内容完全一样的对象字面量也是两个不同引用,所以都会被保留。
function uniqueBy(arr, key) {
return [...new Map(arr.map(item => [item[key], item])).values()];
}
const users = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 1, name: '王五' },
];
console.log(uniqueBy(users, 'id'));
// [{ id: 1, name: '王五' }, { id: 2, name: '李四' }]
// 注意:id=1 保留的是「最后出现」的;想保留第一个看下一段一行变体:遍历时用 Set 记录见过的 key,重复的直接跳过。这个写法还天然保留原数组顺序。
function uniqueByFirst(arr, key) {
const seen = new Set();
return arr.filter(item => {
if (seen.has(item[key])) return false;
seen.add(item[key]);
return true;
});
}
// uniqueByFirst(users, 'id') → [{ id: 1, name: '张三' }, { id: 2, name: '李四' }]先看数据里有没有对象,再看要不要保留首次出现,30 秒选完。
| 写法 | 适用场景 | 注意点 |
|---|---|---|
| [...new Set(arr)] | 原始值数组(含 NaN、null) | 对象数组无效 |
| filter + indexOf | 要兼容极老环境 | O(n²),大数据慢;NaN 去不掉 |
| Map 按 key | 对象数组按唯一字段去重 | 后出现的覆盖先出现的 |
| filter + Set 记录 key | 要保留首次出现 | 需要自己多写几行 |
| reduce 累积 | 去重同时要做变换 | 初始值别忘传 [] |
| JSON.stringify 作 key | 整个对象内容去重 | 字段顺序影响 key;undefined/函数字段会丢 |
写法没选错还是去不掉?按这个顺序查。
下面这些场景几乎每个项目都会遇到一次。
原始值数组用 [...new Set(arr)],一行且 O(n)。对象数组 Set 去不掉,要改用 Map 按 key 去重,两种场景不要混用。
Set 按引用比较,两个内容相同的对象字面量也是不同引用,都会被保留。要么按唯一 key 用 Map,要么把对象 stringify 后再比较。
new Map 会用后出现的覆盖先出现的。改用 filter 加 Set 记录见过的 key,遇到重复直接跳过,即可保留首次出现的元素。
用 Set 不会,它基于 SameValueZero,NaN 也能正确去重;但 filter 加 indexOf 的老写法因 NaN !== NaN 会把 NaN 全部留下。