JavaScript复杂数组去重需避免Set和JSON.stringify,应采用深比较+filter(如lodash.isEqual)或稳定序列化键+Map;含函数、Symbol等特殊值需定制比较策略,生产环境推荐lodash.uniqWith。

JavaScript 中处理包含引用类型(如对象、数组、函数等)的复杂数组去重,不能直接用 Set 或 JSON.stringify 简单粗暴地处理,因为对象的相等性判断基于引用而非内容,而 JSON.stringify 有诸多限制(比如丢失函数、undefined、Symbol、循环引用、属性顺序敏感等)。真正可靠的方式是**自定义比较逻辑 + 遍历筛选**,或借助深比较工具辅助实现。
用深比较 + filter 实现基础去重
核心思路:遍历数组,对每个元素,检查它是否与前面已保留的元素“深度相等”。若不等,则保留;否则跳过。
- 可用
lodash.isEqual做健壮的深比较(推荐用于生产环境) - 若不想引入依赖,可手写轻量级深比较函数(适用于简单嵌套对象/数组)
- 注意性能:O(n²) 时间复杂度,小数据量(
示例(使用 lodash):
import { isEqual } from 'lodash';
const arr = [
{ id: 1, tags: ['a', 'b'] },
[1, 2, { x: 3 }],
{ id: 1, tags: ['a', 'b'] }, // 重复
[1, 2, { x: 3 }], // 重复
];
const uniqueArr = arr.filter((item, index) =>
index === arr.findIndex(el => isEqual(el, item))
);
// 结果保留前两个,剔除后两个重复项
用序列化键 + Map 实现更高效去重(适合中等规模)
将每个引用类型值通过稳定、可预测的方式转为字符串键(称为“序列化键”),再用 Map 记录是否见过该键。关键在于:键必须能区分不同结构,且相同结构生成相同键。
立即学习“Java免费学习笔记(深入)”;
- 避免直接用
JSON.stringify—— 它不处理函数、undefined、循环引用,且对象属性顺序影响结果 - 可用
fast-deep-equal的canonicalize思路,或使用flatted/safe-stable-stringify等更鲁棒的序列化库 - 简易方案:对纯对象/数组组合,先
structuredClone(若支持)+ 排序键 + JSON.stringify(仅作示意,不推荐用于生产)
轻量示例(仅限无函数、无循环、属性顺序不敏感的场景):
function getStableKey(val) {
if (val === null || typeof val !== 'object') return String(val);
if (Array.isArray(val)) return JSON.stringify(val.map(getStableKey));
if (typeof val === 'object') {
const keys = Object.keys(val).sort();
return JSON.stringify(Object.fromEntries(keys.map(k => [k, getStableKey(val[k])])));
}
}
const uniqueByMap = (arr) => {
const seen = new Map();
return arr.filter(item => {
const key = getStableKey(item);
if (seen.has(key)) return false;
seen.set(key, true);
return true;
});
};
处理含函数、Symbol、Date、RegExp 等特殊值
标准 JSON 和多数序列化方法会忽略或错误处理这些类型。若数组中存在这类值,需显式约定比较策略:
-
函数:通常按
fn.toString()比较(注意压缩/格式差异),或统一视为不相等(因行为难判定) -
Symbol:无法被 JSON 序列化,需提取
Symbol.keyFor(仅全局 Symbol)或跳过比较 -
Date / RegExp / Set / Map:应单独判断类型并调用对应方法(如
date.getTime()、regExp.toString()) - 实际项目中,建议先明确业务是否真需要对函数去重——多数情况只需去重配置对象,函数作为方法引用可忽略或单独处理
实用建议与避坑提醒
- 优先用
lodash.uniqWith(arr, isEqual)—— 简洁、健壮、经过大量测试 - 避免在循环中反复调用深比较(如
filter + findIndex + isEqual),可提前缓存已处理项的哈希(如用 WeakMap 存引用 → 键映射) - 若数组极大(>5000 项)且结构固定,考虑预处理:提取唯一标识字段(如
id或uuid),用Map按字段去重,比全量深比较快得多 - 永远不要假设
JSON.stringify(obj1) === JSON.stringify(obj2)能正确判断对象相等 —— 属性顺序、undefined 处理、原型链、不可枚举属性都会导致误判


















