JavaScript中对象深度比较需递归遍历属性并处理循环引用、特殊类型等,不能用===或JSON.stringify;可靠实现需类型识别、WeakMap防循环、统一处理Date/RegExp/数组/对象,并确保键序无关。

JavaScript中判断两个对象是否“相等”,默认的===或==只比较引用,不比较内容。要实现真正的“值相等”判断(即深度比较),需递归遍历所有可枚举属性,并处理嵌套对象、数组、特殊类型(如Date、RegExp)、循环引用等边界情况。
核心思路:递归 + 类型识别 + 循环引用检测
深度比较不是简单地JSON.stringify(a) === JSON.stringify(b)——它会忽略函数、undefined、NaN与NaN不等、属性顺序敏感,且无法处理循环引用。可靠实现需:
- 先做基础类型快速判断(
null、原始值、同一引用) - 统一处理
Date、RegExp、Array、Object等类型,避免原型链干扰 - 对对象/数组递归比较时,用
WeakMap记录已访问的引用对,防止无限循环 - 确保键名顺序不影响结果(例如
{a:1,b:2}和{b:2,a:1}应视为相等)
一个轻量可靠的实现(支持循环引用)
以下是一个生产可用的简化版深度比较函数,不含外部依赖:
function deepEqual(a, b) {
// 1. 基础情况:相同引用、null、NaN 特殊处理
if (a === b) return true;
if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;
if (Number.isNaN(a) && Number.isNaN(b)) return true;
// 2. 类型不一致直接失败
if (Object.prototype.toString.call(a) !== Object.prototype.toString.call(b)) return false;
// 3. 处理 Date 和 RegExp(直接比原始值)
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
if (a instanceof RegExp && b instanceof RegExp) return a.toString() === b.toString();
// 4. 使用 WeakMap 记录已比较的引用对,防循环
const seen = new WeakMap();
function compare(x, y) {
if (x === y) return true;
if (x === null || y === null || typeof x !== 'object' || typeof y !== 'object') return false;
const cached = seen.get(x);
if (cached && cached === y) return true;
seen.set(x, y);
// 数组:长度+逐项比较
if (Array.isArray(x) && Array.isArray(y)) {
if (x.length !== y.length) return false;
for (let i = 0; i < x.length; i++) {
if (!compare(x[i], y[i])) return false;
}
return true;
}
// 普通对象:获取所有自有可枚举属性(含 symbol)
const keysX = Reflect.ownKeys(x);
const keysY = Reflect.ownKeys(y);
if (keysX.length !== keysY.length) return false;
for (const key of keysX) {
if (!Object.prototype.hasOwnProperty.call(y, key)) return false;
if (!compare(x[key], y[key])) return false;
}
return true;
}
return compare(a, b);
}
使用注意事项与常见陷阱
实际使用中需注意:
立即学习“Java免费学习笔记(深入)”;
-
函数和 Symbol 属性默认不参与比较:上述实现包含
Reflect.ownKeys,所以会比较 Symbol 键;但函数值仍按引用比较(除非你主动展开逻辑) -
不可枚举属性被忽略:如
Object.defineProperty(obj, 'x', { value: 1, enumerable: false })中的x不会被检查 -
原型上属性不比较:只检查自有属性(
hasOwnProperty+Reflect.ownKeys保证) - 性能敏感场景慎用:深度比较是 O(n) 时间复杂度,嵌套过深或超大对象会影响响应速度;可考虑加深度限制或用 immutable 数据结构替代
更稳妥的选择:用成熟库
若项目允许引入依赖,推荐使用经过充分测试的方案:
-
Lodash 的
_.isEqual:支持 Map/Set/TypedArray/BigInt/自定义比较器,自动处理循环引用 - fast-deep-equal:极简、无依赖、性能优秀,适合打包体积敏感场景
-
remeda 的
R.equals:函数式风格,支持占位符和部分应用
自己造轮子适合学习和特定约束场景;工程化项目优先选稳定库,减少边界 case 漏洞风险。










