深拷贝防循环引用的核心是用 WeakMap 缓存已处理对象或直接使用 structuredClone()。structuredClone() 自动处理循环引用并保留内置类型;手写需 WeakMap 记录映射;Lodash 的 _.cloneDeep 稳定但增体积;禁用 JSON 方案、普通 Map 及简单相等判断。

JS 深拷贝防止循环引用导致崩溃,核心是“不重复处理同一个对象”。一旦遇到已开始克隆的对象,直接返回它的克隆体,就能切断递归链,避免栈溢出或死循环。
用 structuredClone() 一行解决(现代首选)
浏览器(Chrome 98+、Firefox 94+)和 Node.js(v17.0+)已原生支持 structuredClone(),它内部自动维护对象映射表,识别并跳过重复引用:
- 自动处理循环引用:克隆后
clone.self === clone依然成立 - 保留 Date、RegExp、Map、Set、BigInt 等类型,不转成空对象或字符串
- 不复制函数、DOM 节点、Symbol 和 undefined(这些本就不在结构化克隆范围内)
- 写法极简:
const copy = structuredClone(original);
手写 deepClone + WeakMap 缓存(兼容旧环境或需定制)
当需要支持函数、Symbol、undefined,或运行在不支持 structuredClone 的环境时,手动实现更可控。关键在于用 WeakMap 记录“原始对象 → 克隆对象”的映射:
-
WeakMap以对象为 key,天然支持引用比较,且不会阻止垃圾回收 - 每次进入递归前先查表:
if (seen.has(obj)) return seen.get(obj) - 确认要克隆后,立刻
seen.set(obj, clone),再填充属性,确保子级递归能命中 - 对 Array、Date、RegExp、Map、Set 等类型单独构造,不统一用
{} - 函数可选择原样赋值(
typeof val === 'function' ? val : ...),避免触发递归
用 Lodash 的 _.cloneDeep(工程稳妥之选)
如果项目已引入 Lodash,_.cloneDeep() 是经过长期验证的成熟方案:
- 内置循环引用检测与缓存机制,不会栈溢出
- 支持稀疏数组、类数组对象、原型链(可选)、自定义类型等边界情况
- 通过
customizer参数可保留函数或过滤 Symbol:_.cloneDeep(obj, (val) => typeof val === 'function' ? val : undefined) - 代价是增加约 20–30KB 包体积,纯静态页或超轻量项目需权衡
务必避开的错误思路
有些做法看似“修复”,实则无效甚至危险:
- 给
JSON.stringify加 try-catch 并 fallback——JSON 标准本身禁止循环结构,无法序列化就是无法序列化,没有中间态可挽救 - 用普通 Object 或 Map 替代 WeakMap——会导致内存泄漏(key 强引用,阻止原始对象被回收)
- 只检查顶层对象是否相等(
obj === seenObj)却不缓存中间结果——深层嵌套的循环仍会重复进入 - 忽略 Date、RegExp、Map、Set 等类型的特殊构造逻辑,一律
Object.assign({}, obj)——会丢失类型和行为


















