数组去重是 JavaScript 里非常常见的基础需求。无论是接口返回数据合并、标签列表处理、用户选择项去重,还是前端缓存数据整理,都可能遇到重复值。
数组去重看似简单,但不同方法适合的场景并不一样。简单基本类型可以用 Set,需要保留顺序和自定义规则时可以用 filter,需要按对象字段去重时常用 Map 或对象映射。本文把常见方法放在一起对比,帮你选对写法。
Set 去重
Set 是最简单、最推荐的基本类型数组去重方式。它天然不允许重复值。
const list = [1, 2, 2, 3, 3, 4];
const unique = [...new Set(list)];
console.log(unique);
这种写法简洁、可读性好,适合数字、字符串、布尔值等基本类型。对于大多数简单数组去重,优先使用它。
Set 的局限
Set 判断对象是否重复时,比较的是引用地址,而不是对象内容。因此两个内容相同但引用不同的对象,在 Set 里仍然会被认为是不同元素。
const users = [{ id: 1 }, { id: 1 }];
const unique = [...new Set(users)];
console.log(unique.length);
上面的结果仍然是 2。对象数组去重通常要按某个字段判断,比如 id、name 或业务唯一键。

filter 去重
filter 可以配合 indexOf 实现基本类型去重。思路是:只保留第一次出现的位置。
const list = ['a', 'b', 'a', 'c'];
const unique = list.filter((item, index, array) => {
return array.indexOf(item) === index;
});
这种写法兼容性较好,也比较直观。但如果数组很大,indexOf 每次都要查找,性能不如 Set。
includes 累加
也可以通过新数组累加,遇到不存在的值再放进去。
const result = [];
for (const item of list) {
if (!result.includes(item)) {
result.push(item);
}
}
这种写法适合初学者理解,但在大数组上同样存在重复查找问题。实际项目里,简单值去重通常还是 Set 更好。
reduce 去重
reduce 可以把去重逻辑写成累积过程。它适合顺便做转换、统计或复杂处理。
const unique = list.reduce((result, item) => {
if (!result.includes(item)) {
result.push(item);
}
return result;
}, []);
如果只是单纯去重,reduce 不是最简洁的选择。但如果需要在去重时生成新结构,它会更灵活。
对象映射
对于字符串或数字,也可以用普通对象记录是否出现过。
const seen = {};
const unique = [];
for (const item of list) {
if (!seen[item]) {
seen[item] = true;
unique.push(item);
}
}
对象映射性能不错,但要注意键名会被转换成字符串,某些特殊值可能产生冲突。现代项目里更推荐使用 Map。
Map 按字段去重
对象数组去重最常见的方式,是用 Map 按唯一字段保存。比如按用户 id 去重。
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice Updated' }
];
const map = new Map();
for (const user of users) {
map.set(user.id, user);
}
const uniqueUsers = [...map.values()];
这种写法会保留同一个 id 最后一次出现的数据。如果希望保留第一次出现的数据,可以先判断 map.has(user.id) 再写入。
保留第一次
保留第一次出现的对象时,可以这样写:
const map = new Map();
for (const user of users) {
if (!map.has(user.id)) {
map.set(user.id, user);
}
}
到底保留第一次还是最后一次,取决于业务需求。比如接口增量更新时可能希望保留最新数据;用户原始选择顺序则可能希望保留第一次。
按多个字段去重
有时唯一规则不止一个字段,比如按 type 和 id 共同判断。可以拼接一个稳定 key。
const key = `${item.type}:${item.id}`;
拼接 key 时要避免字段里本身包含分隔符导致冲突。更严谨的场景,可以使用明确的序列化方式或业务侧提供唯一 ID。
NaN 和特殊值
Set 可以正确处理 NaN,它会把多个 NaN 视为同一个值。而 indexOf 无法找到 NaN,这会影响 filter 加 indexOf 的去重结果。
const list = [NaN, NaN, 1];
const unique = [...new Set(list)];
如果数组里可能有 NaN、undefined、null 等特殊值,使用 Set 通常更省心。
方法对比
Set 适合基本类型,简洁高效;filter + indexOf 适合理解原理和旧代码维护;reduce 适合去重同时做累积转换;Map 适合对象数组按字段去重;普通对象映射适合简单场景,但要注意键名转换。
实际项目不要为了炫技写复杂方法。能用 Set 解决就用 Set,对象数组则优先用 Map 按业务唯一键处理。
常见错误
第一种错误是用 Set 去重对象数组,以为相同内容会合并。第二种错误是没明确保留第一次还是最后一次。第三种错误是拼接多个字段时没有考虑冲突。第四种错误是大数组上频繁使用 includes 或 indexOf,性能不够理想。
实践建议
写数组去重前,先确认数组元素类型。如果是数字、字符串等基本类型,用 Set;如果是对象,先确认唯一字段,再用 Map;如果去重时还要统计或转换,再考虑 reduce。
数组去重不是只有一种标准答案。选方法时要看数据类型、数据量、保留规则和可读性。清楚这些前提,代码会更短,也更不容易出错。














暂无评论内容