json.stringify 遇循环依赖直接抛错,因检测发生在序列化前;weakmap 可高效识别环状引用;replacer 需配合外部缓存实现安全序列化;推荐使用 safe-stringify 或 structuredclone 替代手动处理。
json.stringify 遇到循环依赖会直接抛出 typeerror: converting circular structure to json,而且这个错误发生在序列化开始前——replacer 函数根本不会被调用。换句话说,它不是“处理失败”,而是“拒绝启动”。识别循环依赖,本质上是在 stringify 执行前主动拦截,而不是指望它自己发现。
循环依赖的本质特征
不是结构复杂,而是存在同一对象在嵌套路径中重复出现。比如:
-
obj.a = obj(自引用) -
obj.child = { parent: obj }(双向引用) -
tree.nodes[0].parent = tree.root; tree.root.children = [tree.nodes[0]](树形结构中 parent ↔ children 形成闭环)
用 WeakMap 快速检测是否含环
WeakMap 是理想工具:键是对象引用,不阻止垃圾回收,且天然支持对象身份比对(===)。检测逻辑清晰:
- 递归遍历对象所有可枚举自有属性
- 每遇到一个非 null 对象,先查 WeakMap 是否已存过该引用
- 若已存在,立刻返回
true;否则存入并继续深入
示例函数:
DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
function hasCircular(obj) {
if (obj === null || typeof obj !== 'object') return false;
const seen = new WeakMap();
function walk(val) {
if (val === null || typeof val !== 'object') return false;
if (seen.has(val)) return true;
seen.set(val, true);
for (const key in val) {
if (Object.prototype.hasOwnProperty.call(val, key)) {
if (walk(val[key])) return true;
}
}
return false;
}
return walk(obj);
}
replacer 不是检测器,而是逃生通道
replacer 函数本身不具备检测能力,它只在 stringify 过程中对每个值做“是否保留/替换”的决策。想靠它防错,必须提前用外部状态(如数组或 WeakMap)记录已访问对象:
- 每次进入 replacer,检查当前 value 是否已在缓存中
- 若已存在,返回
undefined(该属性被忽略)或占位符如"[Circular]" - 若未见过,将 value 加入缓存,再返回原值
注意:缓存需在 stringify 调用前初始化,并在结束后清空(如设为 null),避免内存泄漏。
更省心的替代方案
不必从零造轮子。生产环境推荐:
-
safe-stringify:npm 包,自动将循环引用替换为
"[Circular]"字符串,无报错 - structuredClone(现代浏览器/Node.js 17.0+):虽不生成 JSON 字符串,但能安全深拷贝含环对象,之后再 stringify 更稳妥
- 调试时用 console.dir(obj, { depth: null }):Chrome/Firefox 控制台原生支持显示循环引用,标记为
circular










