循环引用会导致传统递归深拷贝栈溢出,因无终止条件而无限递归;WeakMap可有效规避,但需在递归前缓存映射,且须额外处理Date、RegExp、Map等特殊类型及Symbol键。

循环引用会直接让传统递归深拷贝函数崩溃,不是逻辑错误,而是运行时栈溢出——函数还没返回,调用栈就满了。关键不在“能不能拷”,而在“根本停不下来”。
循环引用如何触发栈溢出
当对象存在自引用(obj.self = obj)或互引用(a.b = b; b.a = a)时,普通递归函数没有终止依据:
- 每次进入对象属性,都以为是新对象,继续递归
- 实际反复处理同一个内存地址,形成无限调用链
- 浏览器或 Node.js 在约 10k–20k 层调用后抛出
RangeError: Maximum call stack size exceeded
WeakMap 是最实用的规避方案
它用对象作键、不阻止垃圾回收,天然适配深拷贝场景:
- 在递归调用前就把
源对象 → 克隆对象映射存入 WeakMap - 下次再遇到同一对象,直接返回已存克隆体,跳过整个递归分支
- 不用手动清理,也不怕内存泄漏
必须避开的两个典型陷阱
哪怕用了 WeakMap,实现细节出错仍会失效:
-
seen 参数不能设默认值为
new WeakMap():每次调用都新建映射,循环检测完全失效 - 缓存操作必须放在递归调用之前:如果等子属性拷贝完再 set,重复路径已在内部触发新递归
哪些类型需要额外支持
基础 WeakMap 方案默认只处理普通对象和数组,遇到以下类型会误判或丢失:
- Date、RegExp:需单独 instanceof 判断并构造新实例
- Map、Set、TypedArray、Promise:不在判断分支中,会被当作普通对象遍历,结果错误
- Symbol 键、不可枚举属性:
for...in无法获取,需改用Reflect.ownKeys()

















