鉴于我在一个大的JavaScript对象中有一个循环引用
我试试 JSON.stringify(problematicObject)
而浏览器抛出
"TypeError:将循环结构转换为JSON"
(预计)
那么我想找到这个循环引用的原因,最好使用Chrome开发人员工具?这可能吗?如何在大型对象中查找和修复循环引用?
Tre*_*ack 51
来自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
Aar*_*n V 42
当我发现这个问题时,@ 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
这是我的CircularReferenceDetector类,它输出循环引用值实际所在的所有属性堆栈信息,还显示了罪魁祸首引用的位置。
这对于巨大的结构特别有用,其中关键是哪个值是危害的来源并不明显。
它输出字符串化的循环引用值,但所有对自身的引用都替换为“[Circular object --- fix me]”。
用法:
CircularReferenceDetector.detectCircularReferences(value);
注意: 如果您不想使用任何日志记录或没有可用的记录器,请删除 Logger.* 语句。
技术说明:
递归函数遍历对象的所有属性并测试 JSON.stringify 是否成功。如果它不成功(循环引用),则它通过用某个常量字符串替换 value 本身来测试它是否成功。这意味着如果它成功使用这个替换器,这个值就是被循环引用的值。如果不是,它将递归遍历该对象的所有属性。
同时,它还跟踪属性堆栈,为您提供罪魁祸首值所在的信息。
import {Logger} from "../Logger";
export class CircularReferenceDetector {
    static detectCircularReferences(toBeStringifiedValue: any, serializationKeyStack: string[] = []) {
        Object.keys(toBeStringifiedValue).forEach(key => {
            var value = toBeStringifiedValue[key];
            var serializationKeyStackWithNewKey = serializationKeyStack.slice();
            serializationKeyStackWithNewKey.push(key);
            try {
                JSON.stringify(value);
                Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is ok`);
            } catch (error) {
                Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
                var isCircularValue:boolean;
                var circularExcludingStringifyResult:string = "";
                try {
                    circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
                    isCircularValue = true;
                } catch (error) {
                    Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is not the circular source`);
                    CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
                    isCircularValue = false;
                }
                if (isCircularValue) {
                    throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${Util.joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n`+
                        `Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
                }
            }
        });
    }
    private static replaceRootStringifyReplacer(toBeStringifiedValue: any): any {
        var serializedObjectCounter = 0;
        return function (key: any, value: any) {
            if (serializedObjectCounter !== 0 && typeof(toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
                Logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
                return '[Circular object --- fix me]';
            }
            serializedObjectCounter++;
            return value;
        }
    }
}
export class Util {
    static joinStrings(arr: string[], separator: string = ":") {
        if (arr.length === 0) return "";
        return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
    }
}
"use strict";
const Logger_1 = require("../Logger");
class CircularReferenceDetector {
    static detectCircularReferences(toBeStringifiedValue, serializationKeyStack = []) {
        Object.keys(toBeStringifiedValue).forEach(key => {
            var value = toBeStringifiedValue[key];
            var serializationKeyStackWithNewKey = serializationKeyStack.slice();
            serializationKeyStackWithNewKey.push(key);
            try {
                JSON.stringify(value);
                Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is ok`);
            }
            catch (error) {
                Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
                var isCircularValue;
                var circularExcludingStringifyResult = "";
                try {
                    circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
                    isCircularValue = true;
                }
                catch (error) {
                    Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is not the circular source`);
                    CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
                    isCircularValue = false;
                }
                if (isCircularValue) {
                    throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${Util.joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n` +
                        `Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
                }
            }
        });
    }
    static replaceRootStringifyReplacer(toBeStringifiedValue) {
        var serializedObjectCounter = 0;
        return function (key, value) {
            if (serializedObjectCounter !== 0 && typeof (toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
                Logger_1.Logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
                return '[Circular object --- fix me]';
            }
            serializedObjectCounter++;
            return value;
        };
    }
}
exports.CircularReferenceDetector = CircularReferenceDetector;
class Util {
    static joinStrings(arr, separator = ":") {
        if (arr.length === 0)
            return "";
        return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
    }
}
exports.Util = Util;
您也可以JSON.stringify与try/catch 一起使用 
function hasCircularDependency(obj)
{
    try
    {
        JSON.stringify(obj);
    }
    catch(e)
    {
        return e.includes("Converting circular structure to JSON"); 
    }
    return false;
}
演示
function hasCircularDependency(obj)
{
    try
    {
        JSON.stringify(obj);
    }
    catch(e)
    {
        return e.includes("Converting circular structure to JSON"); 
    }
    return false;
}
这里有很多答案,但我想我应该将我的解决方案添加到其中。它类似于@Trey Mack的答案,但该解决方案需要 O(n^2)。该版本使用WeakMap数组代替,将时间提高到 O(n)。
function isCyclic(object) {
   const seenObjects = new WeakMap(); // use to keep track of which objects have been seen.
   function detectCycle(obj) {
      // If 'obj' is an actual object (i.e., has the form of '{}'), check
      // if it's been seen already.
      if (Object.prototype.toString.call(obj) == '[object Object]') {
         if (seenObjects.has(obj)) {
            return true;
         }
         // If 'obj' hasn't been seen, add it to 'seenObjects'.
         // Since 'obj' is used as a key, the value of 'seenObjects[obj]'
         // is irrelevent and can be set as literally anything you want. I 
         // just went with 'undefined'.
         seenObjects.set(obj, undefined);
         // Recurse through the object, looking for more circular references.
         for (var key in obj) {
            if (detectCycle(obj[key])) {
               return true;
            }
         }
      // If 'obj' is an array, check if any of it's elements are
      // an object that has been seen already.
      } else if (Array.isArray(obj)) {
         for (var i in obj) {
            if (detectCycle(obj[i])) {
               return true;
            }
         }
      }
      return false;
   }
   return detectCycle(object);
}
这就是它实际的样子。
> var foo = {grault: {}};
> detectCycle(foo);
false
> foo.grault = foo;
> detectCycle(foo);
true
> var bar = {};
> detectCycle(bar);
false
> bar.plugh = [];
> bar.plugh.push(bar);
> detectCycle(bar);
true
小智 5
这是针对条件上的@Trey Mack和@Freddie Nfbnm答案的修复程序typeof 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;
}
这是一个从@Aaron V和@user4976005的答案混合而成的 Node ES6 版本,它解决了调用 hasOwnProperty 的问题:
const isCyclic = (obj => {
  const keys = []
  const stack = []
  const stackSet = new Set()
  let detected = false
  const detect = ((object, key) => {
    if (!(object instanceof Object))
      return
    if (stackSet.has(object)) { // it's cyclic! Print the object and its locations.
      const oldindex = stack.indexOf(object)
      const l1 = `${keys.join('.')}.${key}`
      const l2 = keys.slice(0, oldindex + 1).join('.')
      console.log(`CIRCULAR: ${l1} = ${l2} = ${object}`)
      console.log(object)
      detected = true
      return
    }
    keys.push(key)
    stack.push(object)
    stackSet.add(object)
    Object.keys(object).forEach(k => { // dive on the object's children
      if (k && Object.prototype.hasOwnProperty.call(object, k))
        detect(object[k], k)
    })
    keys.pop()
    stack.pop()
    stackSet.delete(object)
  })
  detect(obj, 'obj')
  return detected
})
以下是 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}
Siv*_*ran -1
尝试console.log()在 chrome/firefox 浏览器上使用来确定问题所在。
在 Firefox 上使用 Firebug 插件,您可以逐行调试 javascript。
请参阅下面的循环引用问题示例并已处理:-
// JSON.stringify, avoid TypeError: Converting circular structure to JSON
// Demo: Circular reference
var o = {};
o.o = o;
var cache = [];
JSON.stringify(o, function(key, value) {
    if (typeof value === 'object' && value !== null) {
        if (cache.indexOf(value) !== -1) {
            // Circular reference found, discard key
            alert("Circular reference found, discard key");
            return;
        }
        alert("value = '" + value + "'");
        // Store value in our collection
        cache.push(value);
    }
    return value;
});
cache = null; // Enable garbage collection
var a = {b:1};
var o = {};
o.one = a;
o.two = a;
// one and two point to the same object, but two is discarded:
JSON.stringify(o);
var obj = {
  a: "foo",
  b: obj
};
var replacement = {"b":undefined};
alert("Result : " + JSON.stringify(obj,replacement));
请参阅示例现场演示
| 归档时间: | 
 | 
| 查看次数: | 34470 次 | 
| 最近记录: |