Skip to content

如何检测对象是否循环引用?

Posted on:2024年8月14日 at 12:08

例如下面的场景, 已经出现循环引用了, 如何检测?

const foo = {
  foo: "Foo",
  bar: {
    bar: "Bar",
  },
};

foo.bar.baz = foo; // 循环引用!

检测对象是否循环引用通常是为了防止无限递归,特别是在处理 JSON 序列化、深拷贝或图遍历时。以下是几种常见的检测对象是否循环引用的方法:

1. 使用 Set 进行检测

一种常见的方法是使用 Set 数据结构来跟踪已经访问过的对象。如果在遍历对象时发现某个对象已经在 Set 中存在,就可以确定存在循环引用。

示例代码

function hasCircularReference(obj) {
  const seen = new Set();

  function detect(obj) {
    if (obj && typeof obj === "object") {
      if (seen.has(obj)) {
        return true; // 循环引用
      }
      seen.add(obj);
      for (const key of Object.keys(obj)) {
        if (detect(obj[key])) {
          return true;
        }
      }
    }
    return false;
  }

  return detect(obj);
}

// 测试循环引用
const a = {};
const b = { a };
a.b = b;

console.log(hasCircularReference(a)); // 输出:true

2. 使用 WeakMap 进行检测

WeakMap 也可以用来检测循环引用,它与 Set 类似,但使用 WeakMap 可以避免内存泄漏,因为 WeakMap 的键是弱引用的。

示例代码

function hasCircularReference(obj) {
  const seen = new WeakMap();

  function detect(obj) {
    if (obj && typeof obj === "object") {
      if (seen.has(obj)) {
        return true; // 循环引用
      }
      seen.set(obj, true);
      for (const key of Object.keys(obj)) {
        if (detect(obj[key])) {
          return true;
        }
      }
    }
    return false;
  }

  return detect(obj);
}

// 测试循环引用
const a = {};
const b = { a };
a.b = b;

console.log(hasCircularReference(a)); // 输出:true

3. 使用 JSON 序列化

一种简单的检测方法是尝试将对象序列化为 JSON 字符串,如果对象中存在循环引用,则会抛出错误。这种方法的缺点是会丢失对象中无法序列化的部分。

示例代码

function isCircular(obj) {
  try {
    JSON.stringify(obj);
    return false;
  } catch (e) {
    return true;
  }
}

// 测试循环引用
const a = {};
const b = { a };
a.b = b;

console.log(isCircular(a)); // 输出:true

4. 手动跟踪引用

你可以在遍历对象时手动维护一个引用列表,在遍历过程中检测到已经访问过的对象即可判定是否存在循环引用。这种方法通常较为复杂,但也可以实现。

原文转自:https://fe.ecool.fun/topic/5ee52126-6153-4409-a930-a01d2d313605