检测循环引用的核心是用weakset记录遍历路径,遇重复对象即返回true;需跳过非对象值,用hasownproperty避免原型干扰,遍历后删除当前对象以区分平级重复引用。

直接检测对象是否存在循环引用,核心思路是遍历过程中记录已访问的对象,一旦再次遇到同一个对象,就说明形成了闭环。
用 WeakSet 跟踪已访问对象(推荐)
WeakSet 是最合适的工具——它不会阻止对象被回收,且能正确处理各种引用类型。关键在于:每次进入一个新对象前先检查是否已存在,存在则返回 true;否则加入集合,再递归检查其属性值。
- 只对 object 类型且不为 null 的值做检查,跳过原始值、函数、undefined、null 等
- 遍历时用
for...in+hasOwnProperty,避免原型链干扰 - 递归调用时传入同一个 WeakSet 实例,确保跨层级共享访问记录
注意平级重复引用的误判
如果多个属性指向同一个子对象(比如 obj.a = obj.b = {x: 1}),不能因此判定为循环引用。解决方法是在某一层遍历结束后,把当前对象从集合中移除——这样只标记“正在遍历路径”,而非“全局已见”。
- 在递归调用前
visited.add(obj) - 在 for 循环结束后
visited.delete(obj) - 这种策略能区分“同一对象被多次引用”和“对象引用自己或祖先”
简单可靠的备选方案:JSON.stringify 捕获法
如果只是快速验证、不要求精确定位环的位置,可以用 JSON 序列化试探:
- 执行
JSON.stringify(obj) - 若抛出
TypeError: Converting circular structure to JSON,说明存在循环引用 - 优点是代码极简、无需手动遍历;缺点是无法获取环的具体结构,且会中断执行
实际可用的检测函数示例
结合上述要点,一个健壮的实现如下:
function hasCircularReference(obj) {
const visited = new WeakSet();
function check(val) {
if (val === null || typeof val !== 'object') return false;
if (visited.has(val)) return true;
visited.add(val);
for (let key in val) {
if (val.hasOwnProperty(key) && check(val[key])) {
return true;
}
}
visited.delete(val);
return false;
}
return check(obj);
}
这个函数能准确识别对象自身引用、双向引用、深层嵌套环,也兼容数组、null 属性等常见结构。











