JavaScript数组去重方法总结:Set、filter、reduce与对象映射对比

数组去重是 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。对象数组去重通常要按某个字段判断,比如 idname 或业务唯一键。

JavaScript数组去重教程配图:数据列表与前端处理
数组去重的关键是先明确比较规则:按值去重,还是按对象字段去重。

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);
  }
}

到底保留第一次还是最后一次,取决于业务需求。比如接口增量更新时可能希望保留最新数据;用户原始选择顺序则可能希望保留第一次。

按多个字段去重

有时唯一规则不止一个字段,比如按 typeid 共同判断。可以拼接一个稳定 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)];

如果数组里可能有 NaNundefinednull 等特殊值,使用 Set 通常更省心。

方法对比

Set 适合基本类型,简洁高效;filter + indexOf 适合理解原理和旧代码维护;reduce 适合去重同时做累积转换;Map 适合对象数组按字段去重;普通对象映射适合简单场景,但要注意键名转换。

实际项目不要为了炫技写复杂方法。能用 Set 解决就用 Set,对象数组则优先用 Map 按业务唯一键处理。

常见错误

第一种错误是用 Set 去重对象数组,以为相同内容会合并。第二种错误是没明确保留第一次还是最后一次。第三种错误是拼接多个字段时没有考虑冲突。第四种错误是大数组上频繁使用 includesindexOf,性能不够理想。

实践建议

写数组去重前,先确认数组元素类型。如果是数字、字符串等基本类型,用 Set;如果是对象,先确认唯一字段,再用 Map;如果去重时还要统计或转换,再考虑 reduce

数组去重不是只有一种标准答案。选方法时要看数据类型、数据量、保留规则和可读性。清楚这些前提,代码会更短,也更不容易出错。

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容