创见博客
JS深比较两个对象
七崽爱吃小饼干2026/03/02阅读 1

JS 实现两个对象的深比较

一个递归深比较函数,能对比两个任意类型的值(基本类型、对象、数组、嵌套对象/数组),不仅比较引用地址,还逐层比较所有属性和值是否完全一致。

完整实现代码

javascript
/**
 * 深比较两个值是否完全相等(支持基本类型、对象、数组、null、undefined、循环引用)
 * @param {any} obj1 - 第一个比较值
 * @param {any} obj2 - 第二个比较值
 * @param {WeakMap} [visited] - 内部使用,处理循环引用
 * @returns {boolean} 相等返回 true,不相等返回 false
 */
function deepEqual(obj1, obj2, visited = new WeakMap()) {
  // 1. 严格相等(基本类型、同一引用、null/undefined 直接判断)
  if (obj1 === obj2) return true;

  // 2. 只要有一个不是对象/函数,且第一步不相等,直接返回 false
  if (
    typeof obj1 !== 'object' || obj1 === null ||
    typeof obj2 !== 'object' || obj2 === null
  ) {
    return false;
  }

  // 3. 处理循环引用(防止无限递归)
  if (visited.has(obj1)) {
    return visited.get(obj1) === obj2;
  }
  visited.set(obj1, obj2);

  // 4. 获取所有可枚举属性(包含自身属性,不区分 Symbol/字符串)
  const keys1 = Reflect.ownKeys(obj1);
  const keys2 = Reflect.ownKeys(obj2);

  // 5. 属性数量不一致,直接不相等
  if (keys1.length !== keys2.length) return false;

  // 6. 递归比较每一个属性
  for (const key of keys1) {
    const val1 = obj1[key];
    const val2 = obj2[key];
    
    // 递归深比较子属性
    if (!deepEqual(val1, val2, visited)) {
      return false;
    }
  }

  return true;
}

核心功能说明

这个函数支持所有场景的深比较:

  1. 基本类型:数字、字符串、布尔值、null、undefined、Symbol
  2. 引用类型:普通对象、数组、嵌套对象/数组
  3. 特殊处理:
    • 循环引用对象(不会无限递归崩溃)
    • Symbol 属性
    • 不可枚举属性
    • 严格区分 0 和 -0、NaN 和 NaN

测试用例(验证效果)

javascript
// 1. 基本类型测试
console.log(deepEqual(1, 1)); // true
console.log(deepEqual('a', 'b')); // false
console.log(deepEqual(NaN, NaN)); // true
console.log(deepEqual(0, -0)); // false

// 2. 对象测试
const obj1 = { a: 1, b: { c: [2, 3] } };
const obj2 = { a: 1, b: { c: [2, 3] } };
const obj3 = { a: 1, b: { c: [2] } };
console.log(deepEqual(obj1, obj2)); // true
console.log(deepEqual(obj1, obj3)); // false

// 3. 数组测试
console.log(deepEqual([1, { a: 2 }], [1, { a: 2 }])); // true

// 4. 循环引用测试
const loopObj1 = {};
const loopObj2 = {};
loopObj1.self = loopObj1;
loopObj2.self = loopObj2;
console.log(deepEqual(loopObj1, loopObj2)); // true

关键逻辑拆解

  1. 第一步快速判断:用 === 直接比较,基本类型、同一引用会直接返回结果
  2. 非对象过滤:排除非对象类型,避免递归报错
  3. 循环引用处理:用 WeakMap 记录已比较的对象,防止无限递归
  4. 全属性对比:用 Reflect.ownKeys 获取所有属性(包含 Symbol/不可枚举属性)
  5. 递归比较:逐层对比子属性/子元素,直到最底层基本类型

总结

  1. 这个深比较函数通用且健壮,支持前端开发中所有常见场景
  2. 核心是递归 + 全属性遍历,同时解决了循环引用的坑
  3. 相比 JSON.stringify 比较,能正确处理 undefined、Symbol、函数、循环引用等特殊情况
评论
0/100