深拷贝函数安全处理循环引用的核心是提前识别并跳过递归,必须用weakmap缓存已克隆对象,在递归前查表返回副本,且weakmap需复用、set操作须在遍历属性之前,同时显式支持date、regexp、map、set等内置类型。

深拷贝函数要安全处理含循环引用的复杂对象,核心是“提前识别、跳过递归、保持结构”。不能依赖 JSON 序列化或简单递归,必须引入缓存机制,让同一原始对象只生成一次副本,并在后续遇到时直接复用。
用 WeakMap 缓存已克隆对象
WeakMap 是最合适的缓存工具:它以对象为键,不增加引用计数,不影响垃圾回收,也无需手动清理。
- 每次进入递归前,先查 map.has(obj);命中则直接返回 map.get(obj),不再深入
- 必须在递归调用前 set:即 map.set(obj, cloned) 要放在遍历属性之前,否则子属性递归时仍会重复处理该对象
- WeakMap 实例必须复用——不能写成 function fn(obj, map = new WeakMap()),否则每层调用都新建缓存,形同虚设
显式支持常见内置类型
仅处理 Object 和 Array 不够。实际对象常含 Map、Set、Date、RegExp、Error、TypedArray 等,若不单独判断,会被当作普通对象遍历,导致结构错乱或报错。
- Date / RegExp:直接用 new Date(obj) 或 new RegExp(obj) 构造
- Map / Set:用 new Map([...obj].map(([k, v]) => [k, deepClone(v, map)])) 类似方式重建
- Error:新建 new obj.constructor(obj.message),再复制 name、stack 等属性
- 函数、Symbol、undefined、null:不参与缓存,原样返回
调用方式要保障缓存有效
缓存失效往往不是逻辑错,而是传参或初始化问题。
- 推荐由调用方显式创建并复用 WeakMap:const map = new WeakMap(); deepClone(obj1, map); deepClone(obj2, map);
- 或封装为工厂函数:const createCloner = () => { const map = new WeakMap(); return (obj) => deepClone(obj, map); };
- 避免可变默认参数(如 memo = {}),Python 中同样适用:def fn(x, memo=None): if memo is None: memo = {}
验证是否真存在循环引用
别一出错就归因于循环引用。先确认问题根源,再动手改逻辑。
- 打印关键对象的 id()(Python)或 Object.is(obj1, obj2)(JS),看是否反复出现相同标识
- 顺着引用链走几层,例如 obj.parent.children[0].parent 是否能绕回自身
- 用工具辅助定位:Python 可用 objgraph.show_backrefs([obj], max_depth=3) 查闭环;JS 可用 console.dir(obj) 展开观察引用关系











