JS数组去重关键在于先明确重复定义:按字段(如id)、多字段组合或嵌套结构全量等价;字段级用Map或filter+findIndex,全量深比较推荐lodash.isequal,特殊对象需定制序列化。

JS 中去重包含嵌套引用类型的复杂数组,关键不在“怎么写代码”,而在于先明确“按什么算重复”——是按某个字段(如 id)、多字段组合(如 user.id + timestamp),还是按整个嵌套结构内容完全一致?不同目标对应不同策略,选错方法会导致漏去重或误删。
按唯一标识字段去重(最常用、推荐优先尝试)
适用于对象有稳定业务主键(如 id、sku、email)的场景。不依赖序列化,性能好、语义清晰、兼容循环引用和函数等非 JSON-safe 数据。
- 用
Map保留首次出现的项:Array.from(new Map(arr.map(item => [item.id, item])).values()) - 用
filter + findIndex(保序且易读):arr.filter((item, i) => arr.findIndex(v => v.id === item.id) === i) - 若需多字段联合唯一(如
category和status),构造复合 key:const key = `${item.category}-${item.status}`,再按 key 去重
按嵌套内容全量去重(需语义等价判断)
当没有固定主键,且必须认为 {a:1,b:[2,3]} 和 {b:[2,3],a:1} 是同一个对象时使用。注意:这本质是深比较,不能靠 === 或默认 JSON.stringify 直接解决。
- 轻量方案(数据纯净、无顺序敏感):
先标准化键序(如用fast-json-stable-stringify),再用字符串 Set 去重 - 健壮方案(推荐生产环境):
引入lodash.isequal,配合uniqWith:import { uniqWith, isEqual } from 'lodash'; const unique = uniqWith(arr, isEqual); - 手写简易深比较(仅限简单嵌套):
可封装deepEqual(a, b)判断,再结合filter + findIndex使用,但避免用于深层或高频场景
处理含 Proxy、Date、RegExp 等特殊对象
这些类型无法被 JSON.stringify 正确序列化,直接用字符串方式会失败或丢失信息。此时必须回归语义定义:
- Proxy 对象:运行时无法反射获取
target或handler,可靠做法是在创建时就通过工厂函数 +WeakMap记录元信息,后续按target引用去重 - Date / RegExp:可转为统一字符串表示(如
date.toISOString()、reg.toString()),再参与 key 构造 - 含
undefined、function、Symbol的对象:基本无法安全序列化,应提前约定去重维度(例如只比对可序列化的字段),或改用字段级去重
避坑提醒:别踩这些常见雷区
很多“看似能跑”的写法在边界情况会出错:
-
new Set(arr)对对象数组完全无效——它只看内存地址,两个{id:1}永远不重复 - 直接
JSON.stringify(obj)去重:键序不一致、Date变null、undefined被忽略、循环引用报错 - 用
reduce + some判断深相等:时间复杂度 O(n²),大数据量卡顿明显 - 把去重逻辑耦合进渲染层:应在数据进入 UI 前完成,避免重复计算和状态混乱


















