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

鉴于我有一个大的JavaScript对象的循环引用

我试试JSON.stringify(problematicObject)

浏览器抛出

“TypeError:将圆形结构转换为JSON”

(这是预期的)

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

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); } 

这是testing:

 > a = {} > b = {} > ab = b; ba = a; > isCyclic(a) Object {a: Object} "cycle at a" Object {b: Object} "cycle at b" true 

@ 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 (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 (obj.hasOwnProperty(k)) { detect(obj[k], k); } } keys.pop(); stack.pop(); stackSet.delete(obj); return; } detect(obj, 'obj'); return detected; } 

这里有一些非常简单的testing:

 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

这是我的CircularReferenceDetector类,它输出循环引用的值实际位于的所有属性堆栈信息,并显示罪魁祸首引用的位置。

这对巨大的结构特别有用,因为价值是造成伤害的根源的关键并不明显。

它会输出循环引用的值,但是所有对自身的引用被replace为“[Circular object — fix me]”。

用法:
CircularReferenceDetector.detectCircularReferences(value);

注意:如果您不想使用任何日志logging或没有可用的日志logging程序, 删除Logger。*语句。

技术说明:
recursion函数遍历对象的所有属性,并testingJSON.stringify是否成功。 如果它不成功(循环引用),那么它通过用一些常量stringreplace值本身来testing它是否成功。 这意味着如果它成功使用这个替代品,这个值是被循环引用的值。 如果不是,则recursion地遍历该对象的所有属性。

同时它也跟踪物业堆栈给你的罪魁祸首价值所在的信息。

打字稿

 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}`); } } 

这是针对@Trey Mack@Freddie Nfbnm的解决scheme,在type of typeof obj != 'object'条件下的答案。 相反,它应该testingobj值是否不是对象的实例,以便在检查具有对象熟悉度的值时也可以工作(例如,函数和符号 (符号不是对象的实例,但仍然是地址,btw)) 。

我发布这个答案,因为我不能评论这个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; } 

我只是做了这个。 它可能是肮脏的,但无论如何工作…:P

 function dump(orig){ var inspectedObjects = []; console.log('== DUMP =='); (function _dump(o,t){ console.log(t+' Type '+(typeof o)); for(var i in o){ if(o[i] === orig){ console.log(t+' '+i+': [recursive]'); continue; } var ind = 1+inspectedObjects.indexOf(o[i]); if(ind>0) console.log(t+' '+i+': [already inspected ('+ind+')]'); else{ console.log(t+' '+i+': ('+inspectedObjects.push(o[i])+')'); _dump(o[i],t+'>>'); } } }(orig,'>')); } 

然后

 var a = [1,2,3], b = [a,4,5,6], c = {'x':a,'y':b}; a.push(c); dump(c); 

 == DUMP == > Type object > x: (1) >>> Type object >>> 0: (2) >>>>> Type number >>> 1: (3) >>>>> Type number >>> 2: (4) >>>>> Type number >>> 3: [recursive] > y: (5) >>> Type object >>> 0: [already inspected (1)] >>> 1: (6) >>>>> Type number >>> 2: (7) >>>>> Type number >>> 3: (8) >>>>> Type number 

这告诉cx [3]等于c,并且cx = cy [0]。

或者,对这个function的一点编辑可以告诉你你需要什么…

 function findRecursive(orig){ var inspectedObjects = []; (function _find(o,s){ for(var i in o){ if(o[i] === orig){ console.log('Found: obj.'+s.join('.')+'.'+i); return; } if(inspectedObjects.indexOf(o[i])>=0) continue; else{ inspectedObjects.push(o[i]); s.push(i); _find(o[i],s); s.pop(i); } } }(orig,[])); } 

这是@托马斯的答案适应节点:

 const {logger} = require("../logger") // Or: const logger = {debug: (...args) => console.log.call(console.log, args) } const joinStrings = (arr, separator) => { if (arr.length === 0) return ""; return arr.reduce((v1, v2) => `${v1}${separator}${v2}`); } exports.CircularReferenceDetector = class CircularReferenceDetector { detectCircularReferences(toBeStringifiedValue, serializationKeyStack = []) { Object.keys(toBeStringifiedValue).forEach(key => { let value = toBeStringifiedValue[key]; let serializationKeyStackWithNewKey = serializationKeyStack.slice(); serializationKeyStackWithNewKey.push(key); try { JSON.stringify(value); logger.debug(`path "${joinStrings(serializationKeyStack)}" is ok`); } catch (error) { logger.debug(`path "${joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`); let isCircularValue; let circularExcludingStringifyResult = ""; try { circularExcludingStringifyResult = JSON.stringify(value, this.replaceRootStringifyReplacer(value), 2); isCircularValue = true; } catch (error) { logger.debug(`path "${joinStrings(serializationKeyStack)}" is not the circular source`); this.detectCircularReferences(value, serializationKeyStackWithNewKey); isCircularValue = false; } if (isCircularValue) { throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${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`); } } }); } replaceRootStringifyReplacer(toBeStringifiedValue) { let serializedObjectCounter = 0; return function (key, value) { 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; } } } 

我把Freddie Nfbnm的答案转换成了TypeScript:

 export class JsonUtil { static isCyclic(json) { const keys = []; const stack = []; const stackSet = new Set(); let detected = false; function detect(obj, key) { if (typeof obj !== 'object') { return; } if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations. const oldIndex = stack.indexOf(obj); const l1 = keys.join('.') + '.' + key; const 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 (const 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(json, 'obj'); return detected; } } 

尝试在chrome / firefox浏览器上使用console.log()来确定遇到的问题。

在使用Firebug插件的Firefox上,您可以逐行debugging您的javascript。

更新:

请参阅下面循环参考问题的例子和已经处理的内容:

 // JSON.stringify, avoid TypeError: Converting circular structure to JSON // Demo: Circular reference var o = {}; oo = 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)); 

请参阅示例LIVE DEMO