标准答案

  1. 基础流程:原始值直接返回;对象类型先查 WeakMap,如果已经复制过就返回缓存结果;没有复制过就创建新容器、写入 WeakMap,再递归复制内部值。
  2. 循环引用:WeakMap 用来记录“源对象 -> 克隆对象”的映射。遇到同一个源对象时直接返回已有克隆对象,避免无限递归。
  3. 特殊类型:Date 用时间戳创建新 Date;RegExp 复制 source 和 flags;Map 递归复制 key 和 value;Set 递归复制每个 value;数组和普通对象分别创建对应容器。
  4. 边界取舍:函数、DOM 节点、WeakMap、WeakSet 通常不适合深拷贝。完整深拷贝还要考虑 Symbol 键、不可枚举属性、属性描述符、原型链等。工程里优先使用 structuredClone 或成熟库。

题目解析

重点不是写出最短代码,而是知道深拷贝的边界。循环引用和特殊类型是核心加分点。

WeakMap 的作用不是加速,而是保存已经克隆过的对象映射。它还能避免克隆缓存本身强引用源对象,和深拷贝场景比较匹配。

普通对象和数组只是最基础的情况。真实数据里常见 Date、RegExp、Map、Set,如果实现只处理 plain object,很容易在业务对象里丢类型。

没有一种手写实现可以轻松覆盖所有对象图。只要涉及函数、DOM、文件对象、错误对象、类实例和属性描述符,就需要明确当前实现支持到什么程度。

代码示例

下面是一个覆盖循环引用、Date、RegExp、Map、Set 的核心版深拷贝。

JavaScript
function deepClone(value, cache = new WeakMap()) {
  if (value === null || typeof value !== 'object') {
    return value
  }

  if (cache.has(value)) {
    return cache.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 result = new Map()
    cache.set(value, result)
    value.forEach((mapValue, mapKey) => {
      result.set(deepClone(mapKey, cache), deepClone(mapValue, cache))
    })
    return result
  }

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

  const result = Array.isArray(value) ? [] : {}
  cache.set(value, result)

  Reflect.ownKeys(value).forEach((key) => {
    result[key] = deepClone(value[key], cache)
  })

  return result
}

常见误区

  • 只递归普通对象和数组,忽略循环引用。
  • 用 JSON 方案回答所有深拷贝问题。
  • 复制 Map/Set 时只浅拷贝内部对象。

作者信息