标准答案
- 基础流程:原始值直接返回;对象类型先查 WeakMap,如果已经复制过就返回缓存结果;没有复制过就创建新容器、写入 WeakMap,再递归复制内部值。
- 循环引用:WeakMap 用来记录“源对象 -> 克隆对象”的映射。遇到同一个源对象时直接返回已有克隆对象,避免无限递归。
- 特殊类型:Date 用时间戳创建新 Date;RegExp 复制 source 和 flags;Map 递归复制 key 和 value;Set 递归复制每个 value;数组和普通对象分别创建对应容器。
- 边界取舍:函数、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 时只浅拷贝内部对象。