手写深拷贝需用WeakMap缓存已拷贝对象以解决循环引用:先查缓存,存在则直接返回;不存在则新建副本并立即缓存,再递归处理属性;WeakMap键必须为对象且不阻止GC,避免内存泄漏。

手写深拷贝时遇到循环引用的 Array(或 Object),核心是用“缓存机制”记录已拷贝过的对象,后续再遇到就直接返回已有副本,从而打破递归死循环。
用 WeakMap 缓存已处理的对象
WeakMap 是最佳选择:键必须是对象,且不阻止垃圾回收,适合做临时映射;它天然避免内存泄漏,也支持任意引用类型(包括数组、函数、Symbol 键对象等)。
思路是:每次进入拷贝逻辑前,先查 WeakMap 中是否已有该源对象的对应副本;如果有,直接返回,跳过递归。
- 初始化一个 WeakMap
,在递归函数外层定义 - 对当前要拷贝的值,先
map.has(obj)判断是否处理过 - 若存在,直接返回
map.get(obj),不再深入属性 - 若不存在,新建副本(如
[]或{}),立刻map.set(obj, clone),再遍历赋值——顺序很重要,必须设完再递归,否则子属性遇到自身仍会无限调用
区分可遍历对象与基础类型
不是所有值都需要缓存和递归。需明确分类:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
立即学习“Java免费学习笔记(深入)”;
- null、undefined、原始类型(string/number/boolean/symbol/bigint):直接返回,无需缓存
-
Date、RegExp、Map、Set、ArrayBuffer、TypedArray 等内置类型:单独构造新实例(如
new Date(obj)),不走通用递归,也不进 WeakMap(它们不可被引用为 WeakMap 键,但本身不会形成循环) - 普通对象(plain object)和数组(Array):才需要 WeakMap 缓存 + 递归遍历
示例代码(精简可靠版)
以下是一个能正确处理 let a = []; a.push(a); 这类自循环数组的实现:
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (map.has(obj)) return map.get(obj);
let clone;
if (Array.isArray(obj)) {
clone = [];
} else if (obj instanceof Date) {
clone = new Date(obj);
} else if (obj instanceof RegExp) {
clone = new RegExp(obj);
} else {
clone = {};
}
map.set(obj, clone);
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
clone[i] = deepClone(obj[i], map);
}
} else if (obj instanceof Map) {
obj.forEach((v, k) => clone.set(k, deepClone(v, map)));
} else if (obj instanceof Set) {
obj.forEach(v => clone.add(deepClone(v, map)));
} else {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clone[key] = deepClone(obj[key], map);
}
}
}
return clone;
}
注意:该版本默认不处理 function、Promise、Error 等不可枚举或无意义深拷贝的类型,按需可扩展;WeakMap 作为参数传递确保同一调用链共享缓存,避免嵌套调用中重复创建。
为什么不用 Map 或普通对象做缓存?
用普通对象({})当缓存键是字符串化的,会丢失引用语义——{a:1} 和 {a:1} 被当成相同键;而 Map 虽支持对象作键,但会导致内存泄漏(即使原对象被销毁,Map 仍强持有它)。WeakMap 自动跟随原对象生命周期,安全又精准。

















