有没有办法在JavaScript中testing循环引用?

我正在做一个游戏,我遇到了一个问题…当我试图保存,JSON失败,并报告循环引用正在某处。 我不认为它实际上是,我看不到它,所以有一个algorithm或任何东西可以告诉我究竟(哪些对象和东西之间)? 另外,是否有一个可以保存循环引用的JSON替代方法? 我正在运行一个node.js服务器,我看到这个 ,但是我不能让它工作(它不是作为一个模块,我可以要求()在我的代码)。

如果要序列化一个循环引用以便保存它,则需要将引用设置为“虚拟”,因为它不能被序列化为循环引用,因为这会导致序列化永远序列化同一个对象圈或者至less在运行时间耗尽内存之前)。

因此,不是存储循环引用本身,而是存储一个指向该对象的指针。 指针就像ref : '#path.to.object' ,当你反序列化的时候可以解决这个问题,所以你把引用指向实际的对象。 你只需要打破序列化的参考,以便能够序列化它。

在JavaScript中发现循环引用可以通过迭代遍历所有对象(使用for (x in y) ),在数组中存储x并将每个x与身份运算符(aka 严格比较运算符 )进行比较 ===每个z在临时arrays中。 每当x === z等于true时,用一个占位符replace对x的引用,该占位符将被序列化到上面提到的ref

在“访问”对象上保留一个数组的另一种方法是通过设置一个属性来“污染”你遍历的对象,就像这个非常天真的例子:

 for (x in y) { if (x.visited) { continue; } x.visited = true; } 

没有好的方法来检测对象的循环性,但是通过遍历对象树和检查引用是可能的。 我烘烤了一个节点行走function,试图检测一个节点是否已被用作其父节点

 function isCircularObject(node, parents){ parents = parents || []; if(!node || typeof node != "object"){ return false; } var keys = Object.keys(node), i, value; parents.push(node); // add self to current path for(i = keys.length-1; i>=0; i--){ value = node[keys[i]]; if(value && typeof value == "object"){ if(parents.indexOf(value)>=0){ // circularity detected! return true; } // check child nodes if(arguments.callee(value, parents)){ return true; } } } parents.pop(node); return false; } 

用法将是isCircularObject(obj_value) ,如果循环存在,函数返回true否则返回false

 // setup test object var testObj = { property_a:1, property_b: { porperty_c: 2 }, property_d: { property_e: { property_f: 3 } } } console.log(isCircularObject(testObj)); // false // add reference to another node in the same object testObj.property_d.property_e.property_g = testObj.property_b; console.log(isCircularObject(testObj)); // false // add circular node testObj.property_b.property_c = testObj.property_b; console.log(isCircularObject(testObj)); // true 

关键的一点是, 只有当对象引用是同一个对象引用,而不是另一个对象(即使完全相似)时,对象值与其他值相等。

这是Andris回答的一个小扩展,告诉你第一个圆形元素在哪里,以便相应地处理它。

 function findCircularObject(node, parents, tree){ parents = parents || []; tree = tree || []; if (!node || typeof node != "object") return false; var keys = Object.keys(node), i, value; parents.push(node); // add self to current path for (i = keys.length - 1; i >= 0; i--){ value = node[keys[i]]; if (value && typeof value == "object") { tree.push(keys[i]); if (parents.indexOf(value) >= 0) return true; // check child nodes if (arguments.callee(value, parents, tree)) return tree.join('.'); tree.pop(); } } parents.pop(); return false; } 

如果你不想要一个string,树数组是不必要的。 只要改变原来的function

 return value; 

为圆形物体本身或

 return parents.pop(); 

为其父母。

我正在考虑你想要完成什么,根据你的另一个问题的初始代码。 为什么不做这样的事情呢?

 Player = function() { this.UnitTypeXpower = 2 this.UnitTypeYpower = 7 } UnitTypeXAdd = function(owner) { owner.UnitTypeXpower++; } 

这样,你不必使用循环引用,它可以完成同样的事情。

下面是我用来检测循环引用的代码,它使用了asbjornu接受的答案中提出的技术,每个值都被传递,其引用被保存在一个数组中,以便下一个值可以与那些以前走了。

 function isCircular(obj, arr) { "use strict"; var type = typeof obj, propName, //keys, thisVal, //iterKeys, iterArr, lastArr; if (type !== "object" && type !== "function") { return false; } if (Object.prototype.toString.call(arr) !== '[object Array]') { //if (!Array.isArray(arr)) { type = typeof arr; // jslint sake if (!(type === "undefined" || arr === null)) { throw new TypeError("Expected attribute to be an array"); } arr = []; } arr.push(obj); lastArr = arr.length - 1; for (propName in obj) { //keys = Object.keys(obj); //propName = keys[iterKeys]; //for (iterKeys = keys.length - 1; iterKeys >= 0; iterKeys -= 1) { thisVal = obj[propName]; //thisVal = obj[keys[iterKeys]]; type = typeof thisVal; if (type === "object" || type === "function") { for (iterArr = lastArr; iterArr >= 0; iterArr -= 1) { if (thisVal === arr[iterArr]) { return true; } } // alternative to the above for loop /* if (arr.indexOf(obj[propName]) >= 0) { return true; } */ if (isCircular(thisVal, arr)) { return true; } } } arr.pop(); return false; } 

这个代码在jsfiddle上可用 ,你可以自己testing它。 我也对jsperf进行了一些性能testing。

Array.indexOf仅在Javascript 1.6中引入,请参阅MDN页面

Array.isArray是从Javascript 1.8.5开始引入的,请参阅MDN页面

Object.keys仅从Javascript 1.8.5开始引入,请参阅MDN页面

值得注意的是arguments.callee在严格模式下被弃用和禁止,而不是使用命名函数