JSON.stringify 遇循环引用会直接抛错,因引擎在入口阶段检测到重复内存地址即终止;需用 WeakMap + replacer 提前拦截或改用 structuredClone 等替代方案。

JSON.stringify 遇到循环引用会直接崩溃,不是因为 replacer 函数“没写对”,而是它根本没机会执行——错误发生在序列化入口阶段,引擎检测到重复内存地址就立刻抛出 TypeError: Converting circular structure to JSON。所以识别和拦截必须在 stringify 调用前或内部配合状态管理完成,不能依赖 replacer 单独工作。
先确认是不是真循环,而不是误判
有些结构看似循环,实为合法共享引用(比如多个数组项指向同一个配置对象)。可快速验证:
- 用
console.dir(obj, { depth: null })查看——浏览器控制台已内置循环处理,能展开并标注[Circular] - 尝试
structuredClone(obj)(Chrome 98+ / Firefox 94+)——若成功,说明是可克隆的循环,非破坏性结构 - 检查是否含 DOM 节点、
window、document或某些 observable 对象(如 MobX 的observable包装体),这些自带隐式循环,但无法被 JSON 表达
用 WeakMap + replacer 实现轻量拦截
这是最常用、可控且不污染原始数据的方案。关键在于:replacer 不是“识别”循环,而是配合外部状态“拒绝二次访问同一对象”。
使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
- 必须用
WeakMap(不是 Map 或数组),避免内存泄漏 - 只对
typeof value === 'object' && value !== null做检查,跳过函数、undefined、Symbol 等本就不进 JSON 的值 - 返回
undefined会删掉该字段;返回'[Circular]'或null更利于调试定位 - 示例代码:
const replacer = (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.set(value, true);
}
return value;
};
JSON.stringify(obj, replacer);
别硬刚,优先用更稳的替代方案
当结构复杂、含 Symbol/Proxy/Getter 或需反序列化还原时,自写 replacer 容易翻车。推荐:
-
调试打印:直接用
console.log(obj)或console.dir(obj, { depth: null }),零配置、带交互展开 -
安全克隆+序列化:
JSON.stringify(structuredClone(obj))(现代环境首选,自动扁平化循环) -
可逆序列化:用
flatted(输出仍是标准 JSON 字符串)或json-cycle(生成$ref结构,支持 parse 还原) - 服务端场景:改用 MessagePack 或 Protocol Buffers,它们原生支持引用语义,不卡在 JSON 格式限制里
不复杂但容易忽略:WeakMap 方案有效,前提是它得在每次 stringify 调用前新建,否则跨调用残留状态会导致误判;而 structuredClone 是目前最接近“开箱即用”的原生解法。

















