症状
出现 RangeError: Maximum call stack size exceeded;渲染或解析深层嵌套结构时标签页冻结或崩溃;只在对深嵌套或自引用数据时出现。
诊断
在 DevTools Console 中展开 RangeError 堆栈;同一个函数重复出现通常指向递归,记录触发输入。用最小嵌套或循环输入复现:
node -e "const a = {}; a.self = a; JSON.stringify(a)"
如果抛出循环结构的 TypeError,可确认存在环;RangeError 则确认递归深度。临时加入深度计数器或 visited 集合,观察输入是否比预期更深或存在循环。
根因
每个函数调用消耗一个栈帧。递归深度或相互递归超过引擎限制时,JavaScript 抛出 RangeError。循环数据或缺失终止条件会使深度无界。
修复
// ❌ 无环保护
function clone(value) {
if (typeof value !== 'object' || value === null) return value;
const out = Array.isArray(value) ? [] : {};
for (const key of Object.keys(value)) out[key] = clone(value[key]);
return out;
}
// ✅ 使用 WeakMap 记录已访问对象
function clone(value, seen = new WeakMap()) {
if (typeof value !== 'object' || value === null) return value;
if (seen.has(value)) return seen.get(value);
const out = Array.isArray(value) ? [] : {};
seen.set(value, out);
for (const key of Object.keys(value)) out[key] = clone(value[key], seen);
return out;
}
还可以把尾递归改为循环或显式栈;对不可信输入设置深度上限并返回清晰错误;异步迭代用 await 与定时器让出,而不是递归 Promise 链;图遍历用 WeakSet 打破环。
验证
最小复现不再抛错;循环对象被显式处理;深嵌套输入在预期深度内终止;为原始输入添加回归测试,并在目标浏览器与 Node.js 版本中运行。
增加栈大小只会降低失败频率,不能修复无界遍历。捕获 RangeError 后继续执行可能留下部分处理的数据。