← 返回题目列表

如何手写深度相等 deepEqual?需要注意哪些边界?

中等 第 26 / 27 题 更新于 2026/07/29
手写代码对象比较递归边界

简化版

deepEqual 用来判断两个值结构和值是否相等。基础思路是:先用 Object.is 处理基本类型、NaN 和 -0;如果都是对象,再比较类型、数组长度、对象 key 数量,并递归比较每个属性。边界包括 Date、RegExp、Map、Set、循环引用、函数比较和原型差异。面试版通常先实现普通对象和数组,再说明扩展边界。

详细版

基础版可以覆盖大多数场景:

function deepEqual(a: any, b: any): boolean {
  if (Object.is(a, b)) return true
  if (typeof a !== 'object' || a === null || typeof b !== 'object' || 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 (!Object.prototype.hasOwnProperty.call(b, key)) return false
    if (!deepEqual(a[key], b[key])) return false
  }
  return true
}
边界处理方式
NaNObject.is
-0Object.is
Date比较时间戳
循环引用WeakMap 记录访问对

deepEqual 最重要的是先定义“相等”的语义,不同库对函数、原型、Map/Set 的处理可能不同。

完整版教学

一、深比较考什么

这题考递归、类型判断、对象属性遍历和边界意识。不要一上来就 JSON.stringify,那会漏掉很多情况。

例如对象 key 顺序、undefined、函数、Symbol、循环引用都会让 stringify 方案不可靠。

二、先处理基本类型

Object.is=== 更适合做第一步,因为它能正确区分 NaN-0

Object.is(NaN, NaN) // true
Object.is(0, -0) // false

如果第一步相等,就可以直接返回 true。

三、再处理 null 和对象类型

如果其中一个不是对象,说明基本类型不相等,直接 false。

if (typeof a !== 'object' || a === null) return false
if (typeof b !== 'object' || b === null) return false

注意 typeof null"object",所以必须单独判断。

四、数组和普通对象

数组要比较长度和每一项;普通对象要比较 key 数量、key 是否都存在、每个值是否深度相等。

Object.keys 只拿可枚举自有字符串 key。如果要支持 Symbol,需要额外处理。

五、特殊对象怎么处理

Date、RegExp、Map、Set 都不是普通对象。基础版本可以不实现,但要说明。

类型比较方式
DategetTime()
RegExpsourceflags
Mapsize 和键值对
Setsize 和成员

Map/Set 的深比较会更复杂,因为对象 key 和成员可能也要深比较。

六、循环引用怎么处理

如果对象互相引用,递归会死循环。可以用 WeakMap 记录已经比较过的对象对。

function deepEqualSafe(a: any, b: any, seen = new WeakMap<object, object>()) {
  if (Object.is(a, b)) return true
  if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false
  if (seen.get(a) === b) return true
  seen.set(a, b)
  return true
}

这里只展示思路,完整实现还要接上属性递归。

七、常见误区与追问

  • 误区:JSON.stringify 可以做深比较。 它会受 key 顺序影响,也处理不了循环引用、函数、undefined 和 Symbol。
  • 误区:对象 key 数量一样就相等。 还要比较 key 是否一致和值是否深度相等。
  • 误区:数组也是对象,可以完全不单独处理。 数组顺序和长度语义明确,最好单独判断。
  • 追问:NaN 怎么比较?Object.is(NaN, NaN) 返回 true。
  • 追问:循环引用怎么办? 用 WeakMap 记录已比较对象对,避免无限递归。
  • 追问:函数怎么比较? 通常按引用比较,不比较函数源码字符串。

八、加强记忆

deepEqual 记成“四步走”:Object.is、类型/null、数组/对象结构、递归属性。高级边界是特殊对象和循环引用,面试里主动说出这些就很加分。