JavaScript数组去重含函数的复杂对象时,不能依赖JSON.stringify或Set,须基于自定义相等逻辑:按关键属性(如id)用Map缓存、序列化非函数字段后比对,或谨慎使用引用+浅/深比较,核心是明确定义“重复”的语义。

JavaScript 数组去重时,若元素是包含函数的复杂对象(如含方法、闭包、原型链等),无法用 JSON.stringify 或 Set 直接处理,因为函数在序列化中会被忽略或转为 undefined,导致误判相等。关键在于:**不能依赖值的“字符串表示”,而要基于自定义的相等逻辑做比较**。
理解为什么常规方法失效
以下方式都不可靠:
-
[...new Set(arr)]:对象引用不同即视为不重复,哪怕内容完全一样; -
JSON.stringify():函数、undefined、Symbol、Date、RegExp 等会被丢弃或转成空对象/字符串; -
arr.filter((item, i) => arr.findIndex(v => JSON.stringify(v) === JSON.stringify(item)) === i):同上,丢失函数和不可序列化字段。
按需选择去重策略
没有银弹,需根据你的“重复”定义来选:
-
只关心特定属性(推荐):若对象中只有几个字段(如
id、name)决定唯一性,函数和其他字段可忽略——用Map按键缓存:
const uniqueById = arr => {
const seen = new Map();
return arr.filter(item => {
const key = item.id; // 或组合 key: `${item.id}-${item.type}`
if (seen.has(key)) return false;
seen.set(key, true);
return true;
});
};
- 浅比较可序列化部分 + 忽略函数:提取并序列化你关心的字段(排除函数),再比对:
const serializeForDedupe = obj => {
const plain = {};
for (const key in obj) {
if (typeof obj[key] !== 'function') {
plain[key] = obj[key];
}
}
return JSON.stringify(plain);
};
const uniqueByContent = arr => {
const seen = new Set();
return arr.filter(item => {
const key = serializeForDedupe(item);
if (seen.has(key)) return false;
seen.add(key);
return true;
});
};
-
深比较(谨慎使用):若必须考虑嵌套结构且不含循环引用,可用 Lodash 的
isEqual,或手写简易深比较(但不处理函数——函数只能比引用):
// 简化版:仅处理普通对象/数组,函数只做引用比较
const deepEqual = (a, b) => {
if (a === b) return true;
if (typeof a !== 'object' || typeof b !== 'object' || a == null || b == null) return false;
if (Array.isArray(a) !== Array.isArray(b)) return false;
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (const key of keysA) {
if (!keysB.includes(key)) return false;
if (typeof a[key] === 'function' && typeof b[key] === 'function') {
if (a[key] !== b[key]) return false; // 函数必须是同一引用
} else if (typeof a[key] === 'object' && typeof b[key] === 'object') {
if (!deepEqual(a[key], b[key])) return false;
} else if (a[key] !== b[key]) return false;
}
return true;
};
const uniqueByDeepEqual = arr => {
return arr.filter((item, i) =>
arr.slice(0, i).every(prev => !deepEqual(prev, item))
);
};
实际建议与注意事项
多数业务场景不需要真正“深去重含函数的对象”。请先问自己:
立即学习“Java免费学习笔记(深入)”;
- 这些对象是数据载体,还是带行为的实例?如果是后者,通常应避免放入数组统一去重,改用 Map / WeakMap 管理实例;
- 是否真的需要保留函数?很多时候可把逻辑抽离到工具函数,对象只存配置;
- 性能敏感吗?深比较或
filter + every是 O(n²),大数据量时务必用 Map 缓存键; - 注意原型属性:
for...in会遍历继承属性,必要时用Object.prototype.hasOwnProperty.call(obj, key)过滤。
不复杂但容易忽略:函数本身不可序列化,也不可跨上下文相等,所以“对象含函数时去重”的本质,是你得明确——哪些部分定义了它的唯一性,然后只比那些部分。


















