我正在制作游戏,而且我遇到了一个问题...当我尝试保存时,JSON失败并报告正在某处制作循环引用。我不认为它实际上是,我看不到它,所以是否有算法或任何可以告诉我它究竟在哪里(在哪些对象和东西之间)?另外,是否有可以保存循环引用的JSON替代方案?我正在运行一个node.js服务器,我看到了this,但我无法让它工作(它不是作为我可以在我的代码中需要()的模块。
答案 0 :(得分:12)
如果要序列化循环引用以便保存它,则需要将引用设置为“虚拟”,因为它不能被序列化为循环引用,因为这会导致序列化序列化相同的循环对象永远(或至少直到运行时内存不足)。
因此,您只需存储指向对象的指针,而不是存储循环引用本身。指针将类似于ref : '#path.to.object'
,可以在反序列化时解析,以便将引用指向实际对象。您只需要打破序列化的引用即可序列化它。
在JavaScript中发现循环引用可以通过递归遍历所有对象(使用for (x in y)
),将x
存储在数组中并将每个x
与标识运算符(又名)进行比较来完成临时数组中每个===
的{{3}})z
。每当x === z
等于true时,将x
的引用替换为将序列化为上述ref
的占位符。
将数组保留在“已访问”对象上的替代方法是通过在其上设置属性来“污染”您迭代的对象,就像在这个非常天真的示例中一样:
for (x in y) {
if (x.visited) {
continue;
}
x.visited = true;
}
答案 1 :(得分:8)
没有很好的方法来检测对象中的圆度,但是可以通过遍历对象树并检查引用来实现。我编写了一个节点行走函数,它试图检测一个节点是否已被用作其父节点
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
关键点是,如果对象值是相同的对象引用,则对象值等于其他值 ,而不是当它是另一个对象时(即使完全相似)。
答案 2 :(得分:5)
这是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;
}
如果您不想要字符串,则不需要树阵列。只需将原始功能更改为
即可return value;
表示圆形对象本身或
return parents.pop();
为其父母。
答案 3 :(得分:1)
根据您的其他问题的初始代码,我正在考虑您要完成的任务。为什么不这样做呢。
Player = function()
{
this.UnitTypeXpower = 2
this.UnitTypeYpower = 7
}
UnitTypeXAdd = function(owner)
{
owner.UnitTypeXpower++;
}
这样你就不必使用循环引用,它就能完成同样的事情。
答案 4 :(得分:1)
这是我用来检测循环引用的代码,它使用accepted answer by 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上找到,您可以在此自行测试。 我还在jsperf上运行了一些性能测试。
Array.indexOf
仅在Javascript 1.6中引入,请参阅MDN page
Array.isArray
仅在Javascript 1.8.5中引入,请参阅MDN page
Object.keys
仅在Javascript 1.8.5中引入,请参阅MDN page
值得注意的是, arguments.callee
在严格模式下被弃用和禁用,而不是使用命名函数