Skip to content

怎么写深拷贝? ​

深拷贝是新建一份数据,改副本不会影响原对象。日常优先用 structuredClone。JSON.parse(JSON.stringify()) 只能处理纯数据。手写时用 WeakMap 记住已经拷过的对象,才能对付循环引用。

原理 ​

JSON 方案会丢掉 undefined、函数、symbol,把 Date 变成字符串,把 NaN 和 Infinity 变成 null,遇到循环引用直接抛错。BigInt 也会抛错。它适合配置、接口缓存这种纯 JSON。

structuredClone 支持循环引用、Date、Map、Set、ArrayBuffer 和更多内建对象,不支持函数和 DOM 节点。浏览器和当前的 Node 都有。

手写版本按类型分发,并在往下走之前把「原对象 → 副本」放进 WeakMap:

js
function deepClone(value, seen = new WeakMap()) {
  if (typeof value !== "object" || value === null) return value
  if (seen.has(value)) return seen.get(value)
  if (value instanceof Date) return new Date(value.getTime())
  if (value instanceof RegExp) return new RegExp(value.source, value.flags)

  if (value instanceof Map) {
    const copy = new Map()
    seen.set(value, copy)
    value.forEach((item, key) => {
      copy.set(deepClone(key, seen), deepClone(item, seen))
    })
    return copy
  }

  if (value instanceof Set) {
    const copy = new Set()
    seen.set(value, copy)
    value.forEach((item) => copy.add(deepClone(item, seen)))
    return copy
  }

  const copy = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value))
  seen.set(value, copy)
  for (const key of Reflect.ownKeys(value)) {
    copy[key] = deepClone(value[key], seen)
  }
  return copy
}

WeakMap 的键是对象,键不再被别人引用时,这条记录可以一起回收,不会把所有拷贝过的对象永远留住。

追问 ​

浅拷贝和深拷贝的界限?

浅拷贝只复制第一层。{ ...obj } 和 Object.assign 里的对象属性仍然和原对象共享。改 copy.user.name 会改到原来的 user。

** getter 怎么办?**

上面的实现在读取时执行 getter,副本上保存的是当时的值。要连访问器一起复制,就要拷贝属性描述符,并约定 getter 里不会依赖外部状态。面试里先说明自己拷的是值。

易错点 ​

  • 先递归再记入 WeakMap,循环引用会栈溢出。必须先登记副本。
  • typeof null 是 "object",要单独排除。
  • 函数、DOM 节点没有通用的深拷贝语义。碰到它们要明确拒绝或原样返回,不要假装拷贝成功。

相关 ​