weakmap 是最优选择,因其键必须为对象且不阻止垃圾回收,可避免内存泄漏;关键操作顺序为先查表命中则返回,未命中则立即占位写入再递归;需单独处理 date、regexp、map、set、array 和普通对象等类型。

核心是用 WeakMap 记录“原对象 → 已拷贝对象”的映射,每次递归前先查表。命中就直接返回,不进递归;未命中才新建对象、存入映射、再继续递归填充——这样就能打断循环链,避免无限调用。
为什么 WeakMap 是最优选择
WeakMap 的键必须是对象,且不会阻止垃圾回收,天然适配“只缓存对象引用”的场景。相比 Map,它不会造成内存泄漏:当原对象被销毁后,WeakMap 中对应的条目会自动清理。而深拷贝过程中我们只关心对象是否已被处理过,不需要遍历或强持有,WeakMap 正好满足这两个关键约束。
关键操作顺序不能错
- 进入函数第一件事:检查 hash.has(obj),存在就直接 return hash.get(obj)
- 若不存在,先为该对象创建一个占位副本(比如空对象、空数组或 new Date() 等),并立即 hash.set(obj, copy)
- 再递归处理属性或元素。这一步很重要——占位提前写入,才能确保后续遇到同一对象时能命中
哪些类型需要单独判断和处理
WeakMap 解决的是“重复进入同一对象”的问题,但不同构造器实例的拷贝逻辑不同。需在查表之后、递归之前显式分支:
-
Date:用
new Date(obj.getTime())创建新实例 -
RegExp:用
new RegExp(obj.source, obj.flags) - Map / Set:新建实例后,用 forEach + 递归分别拷贝 key 和 value(Map)或 value(Set)
-
Array:用
[]创建,再逐项递归赋值 -
普通对象:用
Object.create(Object.getPrototypeOf(obj))保留原型链,再遍历自有属性(含 Symbol)拷贝
一个典型循环引用示例验证
比如:const obj = { a: 1 }; obj.self = obj;。没有 WeakMap 时,deepClone(obj) 会不断调用 deepClone(obj.self) → 又回到 obj → 无限循环。加入 WeakMap 后,第一次进 obj 时创建空对象并缓存;第二次遇到 obj.self(即 obj),查表命中,直接返回已存的空对象,递归终止。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











