WeakMap 优化深拷贝内存的核心在于弱引用键实现自动垃圾回收,仅缓存引用类型对象,且必须在创建副本后立即缓存,避免重复递归;缓存实例需复用而非每次新建。

用 WeakMap 优化深拷贝工具的内存开销,核心是让它只“临时记住”正在处理的对象,且不阻碍垃圾回收。它不是为了省几 KB 内存,而是防止长期运行时缓存堆积、对象无法释放——尤其在频繁拷贝大型嵌套结构或循环引用对象的场景下。
为什么 WeakMap 能降低内存压力
普通 Map 或对象做缓存时,会强持有原对象引用。即使你已经不再使用那个原始对象,只要缓存还存着它的映射,GC 就不能回收它。WeakMap 不会这样:它的键是弱引用,一旦原始对象外部没有其他变量指向它,GC 就能立刻清理掉整个条目,连带释放对应的副本(如果副本也没被其他地方引用)。
- Map 缓存 → 原对象“卡住”不释放 → 内存缓慢上涨
- WeakMap 缓存 → 原对象一丢,缓存自动消失 → 内存自然回落
只对真正需要的对象做缓存
不是所有值都要进 WeakMap。基础类型(string/number/boolean/null/undefined/Symbol/BigInt)和函数直接返回,不参与递归也不进缓存。只对可能形成循环引用的引用类型登记:
- 普通对象、数组
- Date、RegExp、Map、Set、TypedArray、ArrayBuffer、DataView
- 注意:函数默认不缓存,因为 JS 中无法可靠深拷贝函数内部状态
缓存时机必须精准:先存,再递归
这是最容易出错的一环。必须在创建新副本后、还没开始遍历属性前,立刻执行 cache.set(original, clone)。否则,在多条引用路径交汇时(比如 obj.a = obj.b = target),target 会被重复进入递归,缓存完全失效,还可能引发栈溢出。
- ✅ 正确顺序:查缓存 → 未命中 → new 出 clone → 立即 cache.set → 再遍历属性并递归子项
- ❌ 错误顺序:查缓存 → 未命中 → new 出 clone → 遍历+递归全部完成 → 最后才 cache.set
避免缓存实例被反复重建
WeakMap 实例不能写成函数参数默认值,否则每次调用 deepClone 都新建一个空 WeakMap,缓存逻辑彻底失效。
- ❌ 错误:
function deepClone(obj, cache = new WeakMap()) { ... } - ✅ 推荐方式一:外部创建,显式传入
const cache = new WeakMap(); deepClone(obj, cache); - ✅ 推荐方式二:闭包封装,复用同一 cache
const createCloner = () => { const cache = new WeakMap(); return (obj) => { ... }; };
不支持遍历反而是一种优势
WeakMap 没有 keys()、values()、size 等方法,也不能 for...of 遍历。这看似不便,实则强化了它的定位:它不是通用存储,而是轻量、临时、生命周期绑定的映射容器。你不该去“查它存了什么”,而只需相信——该留的留,该走的走,无需手动清理。

















