JS数组去重本质是JavaScript类型系统与开发者意图持续对齐的过程,涵盖原始值(需处理NaN)、对象数组(须按字段去重)、混合类型(如1与'1'区分)等多场景,应依需求选方案而非迷信一行代码。

JS 数组去重不是“写对就行”的小技巧,而是随着语言演进、数据形态变化和工程需求升级,不断被重新定义的过程。它从早期手动双循环起步,到 ES6 Set 的普及,再到对象数组、混合类型、NaN 和类型歧义等边界问题的系统应对,本质是 JavaScript 类型系统与开发者意图之间持续对齐的过程。
原始值去重:从 O(n²) 到 O(n) 的效率跃迁
早期常用 for + indexOf 或 filter + indexOf 组合,逻辑清晰但时间复杂度为 O(n²),适合小数组或教学场景:
-
[1,1,'a','a'].filter((x,i,arr) => arr.indexOf(x) === i)—— 依赖首次索引判断,简洁直观 - 注意 NaN 无法被识别:因为
NaN !== NaN,indexOf(NaN)总返回 -1,导致重复 NaN 全部保留 - ES6 后推荐
[...new Set(arr)]或Array.from(new Set(arr)):自动处理 NaN、undefined、null,且保持插入顺序,时间复杂度降至 O(n)
对象数组去重:不能靠引用,得靠语义
Set 对对象无效,因为 {a:1} !== {a:1};直接用原始值方案会把所有对象都当成不重复项。实际业务中常见按字段去重(如 id、sku):
- 用
Map缓存键值:例如arr.filter((item, i, a) => !map.has(item.id) && map.set(item.id, 1)) - 用
reduce+ 对象哈希:arr.reduce((acc, cur) => acc.some(x => x.id === cur.id) ? acc : [...acc, cur], []),但性能较差 - 更健壮的做法是结合
JSON.stringify生成唯一标识,但需注意属性顺序、函数/undefined/Symbol 丢失等限制
混合类型与边界陷阱:1 和 '1' 算不算重复?
原始值去重时,Object 键名隐式转字符串,导致 {1: true, '1': true} 实际只剩一个键 —— 这是很多“对象哈希法”出错的根源:
- 若需区分数字
1和字符串'1',必须显式拼接类型:typeof item + '|' + item - 对
null、undefined、Symbol等,String(null)是"null",String(undefined)是"undefined",可安全参与拼接 - Set 方案天然支持类型区分,
new Set([1, '1'])返回两个元素,无需额外处理
现代推荐:按场景选方案,不迷信“一行代码”
没有银弹,只有适配:
- 纯原始值(含 NaN)、无兼容要求 →
[...new Set(arr)] - 需兼容 IE 或旧环境 →
filter + indexOf+ 单独处理 NaN(如先用arr.map(x => x !== x ? 'NaN' : x)转义) - 对象数组按某字段去重 →
Map或reduce配合查找逻辑 - 深层结构去重或需自定义相等逻辑 → 封装工具函数,接受比较器(如 Lodash 的
uniqBy)











