检测对象是否存在循环引用的核心是用WeakMap递归遍历并记录已访问对象,若同一对象被重复访问即判定为循环;需仅处理object类型、跳过原型链和非对象值,避免误判共享引用。

检测一个对象是否存在循环属性指向,核心是判断对象在属性链中是否间接引用了自身。这不是靠肉眼观察,而是通过遍历+记录已访问对象来识别“回头路”。
用 WeakMap 递归遍历是最稳妥的方式
WeakMap 能安全存储对象引用,不干扰垃圾回收,适合长期跟踪。每次进入一个新对象前,先查它是否已在 WeakMap 中;若已存在,说明路径绕回来了,即存在循环。
- 只处理
typeof obj === 'object'且非null的值 - 对每个自有属性(
hasOwnProperty)递归检查,跳过原型链干扰 - 遇到函数、Symbol、undefined 等非对象类型直接跳过,不参与循环判定
避免误判:同引用多次出现 ≠ 循环引用
比如两个属性都指向同一个子对象:obj.a = child; obj.b = child; 这不是循环,只是共享引用。所以不能简单用 Set 存所有值,而要在递归调用时保持同一份 visited 记录(传参或闭包),确保跨层级一致性。
- 错误做法:每层新建 Set,导致同一对象在不同层级被重复加入
- 正确做法:复用同一个 WeakMap 实例贯穿整个检测过程
JSON.stringify 报错只能作为辅助手段
它会因循环引用抛出 TypeError: Converting circular structure to JSON,但局限明显:
- 无法处理函数、Symbol、undefined、BigInt 等类型(直接忽略或报错)
- 一旦对象含这些值,即使没循环也会失败,造成假阳性
- 无法定位具体哪条路径成环,仅能返回“有或无”
复杂嵌套场景建议加深度限制和类型过滤
超深嵌套可能引发栈溢出,尤其在老旧运行时。可在递归中加入层级计数,超过阈值(如 100 层)主动退出并标记“疑似循环”或“结构过深”。
- 对 Map、Set、Date、RegExp 等内置类型,按需决定是否深入(多数业务场景中它们内部不构成 JS 层面的循环)
- 遇到 class 实例,优先检查其可枚举属性,避免被私有字段或 getter 干扰逻辑

















