根本原因是调用栈持续压入无法及时弹出,典型于循环引用或极深嵌套;解决策略包括:①用weakmap拦截循环引用;②改用迭代栈模拟规避递归;③优先使用structuredclone()。

JavaScript 中深拷贝时出现栈溢出(RangeError: Maximum call stack size exceeded),根本原因不是“用了递归”,而是调用栈持续压入、无法及时弹出——典型场景就是处理循环引用或极深嵌套结构时,递归函数反复调用自身却始终达不到退出条件。
要防止这种错误,核心思路是:切断无限递归路径 + 控制执行上下文深度。以下是实际可用的策略:
用 WeakMap 记录已处理对象,拦截循环引用
循环引用(如 obj.self = obj)会让递归陷入死循环。WeakMap 可以安全记录原始对象与对应克隆体的映射,每次进入拷贝前先查表:
- 如果该对象已被克隆过,直接返回缓存结果,不再递归
- WeakMap 键是对象引用,不会阻止垃圾回收,适合做临时追踪
function deepClone(obj, memo = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (memo.has(obj)) return memo.get(obj);
const cloned = Array.isArray(obj) ? [] : {};
memo.set(obj, cloned);
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key], memo);
}
}
return cloned;
}
✅ 有效拦截 a.self = a 类型的自引用
❌ 不解决超深嵌套(比如 10 万层纯对象链)——此时仍可能栈溢出
改用迭代(栈模拟)替代递归,彻底规避调用栈限制
把递归逻辑改造成显式维护一个待处理任务栈,每轮只处理一项,避免引擎调用栈层层累积:
- 创建一个栈数组,每一项包含:源值、目标父容器、键或索引
- 从根节点开始入栈;每次出栈,创建新值并挂载到父级对应位置
- 遇到对象/数组就拆解其属性/元素,逐个入栈(类似 DFS)
这样执行流始终在单层函数内,栈帧数量可控。
function iterativeDeepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
const memo = new WeakMap();
const stack = [{ src: obj, parent: null, key: null }];
let root = null;
while (stack.length > 0) {
const { src, parent, key } = stack.pop();
let dst;
if (Array.isArray(src)) {
dst = [];
} else if (src instanceof Date) {
dst = new Date(src);
} else if (src instanceof RegExp) {
dst = new RegExp(src);
} else {
dst = Object.create(Object.getPrototypeOf(src));
}
if (parent === null) {
root = dst;
} else {
parent[key] = dst;
}
memo.set(src, dst);
if (Array.isArray(src)) {
for (let i = src.length - 1; i >= 0; i--) {
stack.push({ src: src[i], parent: dst, key: i });
}
} else if (src !== null && typeof src === 'object') {
const keys = Object.keys(src);
for (let i = keys.length - 1; i >= 0; i--) {
const k = keys[i];
const val = src[k];
if (val !== null && typeof val === 'object' && !memo.has(val)) {
stack.push({ src: val, parent: dst, key: k });
} else {
dst[k] = val;
}
}
}
}
return root;
}
✅ 彻底避开递归调用栈限制
✅ 同样支持 WeakMap 拦截循环引用
⚠️ 注意:需手动支持 Date、RegExp 等特殊类型(structuredClone 已内置支持)
优先使用 structuredClone() ——现代环境首选方案
如果你的目标环境支持(Chrome 98+、Firefox 94+、Safari 16.4+、Node.js 17.0+),structuredClone() 是最省心的选择:
- 自动处理 Map、Set、Date、RegExp、ArrayBuffer、TypedArray、Error、嵌套对象、循环引用
- 底层用结构化克隆算法(Structured Clone Algorithm),不走 JS 执行栈,无栈溢出风险
- 不支持函数、undefined、Symbol、DOM 节点等非可序列化值(这是设计使然,不是缺陷)
try {
const copy = structuredClone(originalObj);
} catch (err) {
// 处理不可克隆的值,如含函数或 DOM 节点
}
✅ 开箱即用,零配置,最安全
❌ 不能控制拷贝深度,也不能定制行为(如跳过某些字段)
小结:按场景选方案
-
开发新项目、环境可控 → 直接用
structuredClone() - 需兼容旧浏览器或定制逻辑 → 手写迭代版 deepClone(带 WeakMap + 栈模拟)
-
仅需浅层拷贝、确认无嵌套 → 浅拷贝即可(
{...obj}、[...arr]) - 绝对避免递归 → 不要用纯递归实现,哪怕加了深度限制,遇到循环引用仍会崩
关键不是“能不能递归”,而是“有没有兜底机制”。只要确保每个对象最多被处理一次,并且执行路径不依赖引擎调用栈深度,栈溢出问题就能从根本上规避。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











