处理含循环引用的对象深拷贝需用WeakMap缓存已克隆对象,递归前查缓存并提前set,避免重复递归;需显式处理Map、Set、Error等内置类型;WeakMap须复用而非每次新建,默认参数写法会导致缓存失效。

处理含循环引用的对象深拷贝,核心在于“识别重复、跳过递归、保持结构”。不能靠简单 clone 或 JSON 序列化,必须引入对象级缓存机制,让同一源对象只生成一次副本。
用 WeakMap 记录已克隆对象,阻断无限递归
WeakMap 是解决该问题的首选工具,它支持以对象为键、不阻止垃圾回收、无需手动清理。关键逻辑是:每次进入递归前先查 WeakMap;若已存在对应副本,直接返回,不再深入。
- 必须在递归调用 之前 调用
map.set(obj, cloned),否则重复路径仍会触发新递归 - 基本类型(string/number/boolean/null/undefined)和函数、Symbol 等不参与缓存判断,直接返回原值
- 对 Date、RegExp、Array、普通 Object 需分别构造新实例,再递归填充属性
兼容常见内置类型,避免误判或丢失
仅处理 object 和 array 远不够。实际业务中常遇到 Map、Set、TypedArray、Error、Promise 等。若不显式判断,它们会被当作普通对象遍历,导致结构错乱或报错。
- Map/WeakMap:需用
new Map([...obj].map(([k, v]) => [k, deepClone(v, map)])) - Set/WeakSet:类似地用
new Set([...obj].map(v => deepClone(v, map))) - Error:可新建
new obj.constructor(obj.message),并复制 name、stack 等属性 - 避免把 undefined、function、Symbol 当作可递归对象——它们应原样返回
实战中注意调用方式,防止缓存失效
WeakMap 实例必须复用,不能每次调用都新建。否则缓存形同虚设,循环引用依然爆栈。
- 错误写法:
function deepClone(obj, map = new WeakMap())—— 默认参数每次新建,缓存不跨层 - 推荐写法:导出一个工厂函数
const createCloner = () => { const map = new WeakMap(); return (obj) => deepClone(obj, map); } - 或由调用方显式传入 map:
const map = new WeakMap(); deepClone(obj1, map); deepClone(obj2, map);
替代方案对比:MessageChannel 与第三方库
除了手写,还有两种常用路径:
- MessageChannel:利用结构化克隆算法(structured clone),天然支持循环引用、Map/Set/Blob 等,但不支持函数、undefined、RegExp,且只能在浏览器主线程或 Worker 中使用
- lodash.cloneDeep 或 deepcopy(PHP):已内置循环检测与多类型适配,适合快速落地;但体积较大,定制性弱,调试链路长










