实现对象深比较 isEqual

手写 JavaScript 对象深比较函数,递归比较两个值是否相等

问题

实现一个 isEqual 函数,递归比较两个值是否相等,支持对象、数组、基本类型等。

解答

基础版本

function isEqual(a, b) {
  // 严格相等,处理基本类型和同一引用
  if (a === b) return true;

  // 处理 NaN(NaN !== NaN)
  if (Number.isNaN(a) && Number.isNaN(b)) return true;

  // 如果有一个不是对象,或者是 null,直接返回 false
  if (typeof a !== 'object' || typeof b !== 'object') return false;
  if (a === null || b === null) 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 (!isEqual(a[key], b[key])) return false;
  }

  return true;
}

完整版本(处理循环引用)

function isEqual(a, b, visited = new WeakMap()) {
  // 严格相等
  if (a === b) return true;

  // 处理 NaN
  if (Number.isNaN(a) && Number.isNaN(b)) return true;

  // 类型检查
  if (typeof a !== 'object' || typeof b !== 'object') return false;
  if (a === null || b === null) return false;

  // 类型必须一致(区分数组和对象)
  if (Object.prototype.toString.call(a) !== Object.prototype.toString.call(b)) {
    return false;
  }

  // 处理循环引用
  if (visited.has(a)) {
    return visited.get(a) === b;
  }
  visited.set(a, b);

  // 比较键
  const keysA = Object.keys(a);
  const keysB = Object.keys(b);

  if (keysA.length !== keysB.length) return false;

  // 递归比较
  for (const key of keysA) {
    if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
    if (!isEqual(a[key], b[key], visited)) return false;
  }

  return true;
}

测试用例

// 基本类型
console.log(isEqual(1, 1));           // true
console.log(isEqual('a', 'a'));       // true
console.log(isEqual(NaN, NaN));       // true

// 对象
console.log(isEqual({ a: 1 }, { a: 1 }));           // true
console.log(isEqual({ a: 1 }, { a: 2 }));           // false
console.log(isEqual({ a: { b: 1 } }, { a: { b: 1 } })); // true

// 数组
console.log(isEqual([1, 2, 3], [1, 2, 3]));         // true
console.log(isEqual([1, [2]], [1, [2]]));           // true

// 循环引用
const obj1 = { a: 1 };
obj1.self = obj1;
const obj2 = { a: 1 };
obj2.self = obj2;
console.log(isEqual(obj1, obj2));     // true

关键点

  • === 处理基本类型和同一引用,NaN 需要单独判断
  • Object.prototype.toString.call() 区分数组和普通对象
  • 递归比较嵌套属性,先比较键数量再逐个比较
  • WeakMap 记录已访问对象,解决循环引用问题
  • Object.keys() 只遍历自身可枚举属性,不含原型链