检测和修复 JavaScript 中的循环引用

IT技术 javascript
2021-03-18 02:35:01

鉴于我在一个大型 JavaScript 对象中有一个循环引用

我尝试 JSON.stringify(problematicObject)

浏览器抛出

“类型错误:将圆形结构转换为 JSON”

(这是预期的)

那我想找到这个循环引用的原因,最好使用Chrome开发者工具?这可能吗?如何在大对象中查找和修复循环引用?

6个回答

摘自http://blog.vjeux.com/2011/javascript/cyclic-object-detection.html添加了一行以检测循环的位置。将其粘贴到 Chrome 开发工具中:

function isCyclic (obj) {
  var seenObjects = [];

  function detect (obj) {
    if (obj && typeof obj === 'object') {
      if (seenObjects.indexOf(obj) !== -1) {
        return true;
      }
      seenObjects.push(obj);
      for (var key in obj) {
        if (obj.hasOwnProperty(key) && detect(obj[key])) {
          console.log(obj, 'cycle at ' + key);
          return true;
        }
      }
    }
    return false;
  }

  return detect(obj);
}

这是测试:

> a = {}
> b = {}
> a.b = b; b.a = a;
> isCyclic(a)
  Object {a: Object}
   "cycle at a"
  Object {b: Object}
   "cycle at b"
  true
仅仅因为引用被多次使用并不一定意味着它是循环的。var x = {}; JSON.stringify([x,x])很好...虽然var x = {}; x.x = x; JSON.stringify(x);不是。
2021-04-20 02:35:01
这个对 null 的误报并且不是递归的
2021-04-23 02:35:01
@AaditMShah,由于detect在输入对象上被调用,这将导致它立即退出,声称存在直接从输入对象到自身的循环。
2021-04-26 02:35:01
出于某种原因,当我使用此解决方案时,它给了我一个错误:obj.hasOwnProperty is not a function. 这是在浏览器加载的脚本中运行的。
2021-05-05 02:35:01
不要忘记将根 JSON 对象本身添加到seenObjects. 应该是var seenObjects = [obj];
2021-05-13 02:35:01

@tmack 的答案绝对是我发现这个问题时所寻找的!

不幸的是,它返回许多误报 - 如果对象在 JSON 中复制,则返回 true,这循环性不同。循环意味着一个对象是它自己的孩子,例如

obj.key1.key2.[...].keyX === obj

我修改了原始答案,这对我有用:

function isCyclic(obj) {
  var keys = [];
  var stack = [];
  var stackSet = new Set();
  var detected = false;

  function detect(obj, key) {
    if (obj && typeof obj != 'object') { return; }

    if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
      var oldindex = stack.indexOf(obj);
      var l1 = keys.join('.') + '.' + key;
      var l2 = keys.slice(0, oldindex + 1).join('.');
      console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
      console.log(obj);
      detected = true;
      return;
    }

    keys.push(key);
    stack.push(obj);
    stackSet.add(obj);
    for (var k in obj) { //dive on the object's children
      if (Object.prototype.hasOwnProperty.call(obj, k)) { detect(obj[k], k); }
    }

    keys.pop();
    stack.pop();
    stackSet.delete(obj);
    return;
  }

  detect(obj, 'obj');
  return detected;
}

下面是一些非常简单的测试:

var root = {}
var leaf = {'isleaf':true};
var cycle2 = {l:leaf};
var cycle1 = {c2: cycle2, l:leaf};
cycle2.c1 = cycle1
root.leaf = leaf

isCyclic(cycle1); // returns true, logs "CIRCULAR: obj.c2.c1 = obj"
isCyclic(cycle2); // returns true, logs "CIRCULAR: obj.c1.c2 = obj"
isCyclic(leaf); // returns false
isCyclic(root); // returns false
添加记忆以使这个 O(N) 留给读者作为练习:)
2021-04-30 02:35:01
我添加了这个答案的 TypeScript 版本作为新答案。见下文。
2021-05-02 02:35:01
@Willwsharp - 已经有一段时间了,但我相信这个想法是肯定有可能多次遇到同一个对象,即使在非圆形结构中也是如此。所以,在最坏的情况下,我在这里编写的代码会一遍又一遍地处理相同的对象,从而不必要地 O(N^2)。您可以使用记忆来存储每个对象的子对象并防止重新处理。
2021-05-02 02:35:01
if (typeof obj != 'object') { return; }应该是if (obj && typeof obj != 'object') { return; }因为typeof null == "object"
2021-05-06 02:35:01
@AaronV 哦太棒了!我做了更多的工作,得出了同样的结论,很高兴听到我走在正确的道路上;谢谢!
2021-05-14 02:35:01

以下是 MDNJSON.stringify()在循环对象上使用时检测和修复循环引用的方法https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value

在如下所示的圆形结构中

var circularReference = {otherData: 123};
circularReference.myself = circularReference;

JSON.stringify() 将失败:

JSON.stringify(circularReference);
// TypeError: cyclic object value

要序列化循环引用,您可以使用支持它们的库(例如cycle.js)或自己实现解决方案,这将需要通过可序列化值查找和替换(或删除)循环引用。

下面的代码片段说明了如何使用的替换参数来查找和过滤(从而导致数据丢失)循环引用JSON.stringify()

const getCircularReplacer = () => {
      const seen = new WeakSet();
      return (key, value) => {
        if (typeof value === "object" && value !== null) {
          if (seen.has(value)) {
            return;
          }
          seen.add(value);
        }
        return value;
      };
    };

JSON.stringify(circularReference, getCircularReplacer());
// {"otherData":123}
这是最好的答案。每个人都想编写自己的本土代码。另一方面,MDN 是权威的。如果这段代码太具体(它打算放在 JSON.stringify 中),那么请查看他们引用的代码 cycle.js,它是由知道他们在做什么的人编写的。
2021-05-12 02:35:01

这是对@Trey Mack@Freddie Nfbnmtypeof obj != 'object'条件下回答的修复相反,它应该测试obj值是否不是对象的实例,以便在检查具有对象熟悉度的值时它也可以工作(例如,函数和符号(符号不是对象的实例,但仍然被寻址,顺便说一句))。

我将此作为答案发布,因为我还无法在此 StackExchange 帐户中发表评论。

PS.:请随时要求我删除此答案。

function isCyclic(obj) {
  var keys = [];
  var stack = [];
  var stackSet = new Set();
  var detected = false;

  function detect(obj, key) {
    if (!(obj instanceof Object)) { return; } // Now works with other
                                              // kinds of object.

    if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
      var oldindex = stack.indexOf(obj);
      var l1 = keys.join('.') + '.' + key;
      var l2 = keys.slice(0, oldindex + 1).join('.');
      console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
      console.log(obj);
      detected = true;
      return;
    }

    keys.push(key);
    stack.push(obj);
    stackSet.add(obj);
    for (var k in obj) { //dive on the object's children
      if (obj.hasOwnProperty(k)) { detect(obj[k], k); }
    }

    keys.pop();
    stack.pop();
    stackSet.delete(obj);
    return;
  }

  detect(obj, 'obj');
  return detected;
}

您也可以JSON.stringifytry/catch 一起使用

function hasCircularDependency(obj)
{
    try
    {
        JSON.stringify(obj);
    }
    catch(e)
    {
        return e.includes("Converting circular structure to JSON"); 
    }
    return false;
}

演示

@hudidit 我想将您的评论标记为有趣。此外,对于进一步但同样不重要的信息,我无法将此标记为我自己的评论。
2021-04-18 02:35:01
OP 已经知道这是导致错误的循环引用,他在问题中说I want to find the **cause** of this circular reference(强调我的)。所以你的回答根本没有回答 OP 的问题。
2021-05-07 02:35:01
你是用问题本身来回答问题吗?
2021-05-09 02:35:01