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

时间:2011-09-28 10:45:02

标签: javascript json node.js serverside-javascript circular-reference

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

5 个答案:

答案 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 在严格模式下被弃用和禁用,而不是使用命名函数