Есть ли способ проверить циклическую ссылку в JavaScript?

Я делаю игру, и я столкнулся с проблемой... Когда я пытаюсь сохранить, JSON терпит неудачу и сообщает, что где-то делается круговая ссылка. Я не думаю, что это действительно так, я не вижу его, так есть ли алгоритм или что-нибудь, что могло бы сказать мне, где он точно (между какими объектами и вещами)? Кроме того, есть ли альтернатива JSON, которая может сохранить круговую ссылку? Я запускаю узел.JS сервер, я видел этой, но я не могу заставить его работать (он не сделан как модуль i может требовать() в моем коде).

5 ответов


если вы хотите сериализовать циклическую ссылку, чтобы сохранить ее, вам нужно сделать ссылку "виртуальной", поскольку она не может быть сериализована как циклическая ссылка, так как это приведет к сериализации одного и того же круга объектов навсегда (или, по крайней мере, до тех пор, пока во время выполнения не закончится память).

поэтому вместо хранения самой круговой ссылки вы просто сохраняете указатель на объект. Указатель будет просто что-то вроде ref : '#path.to.object' Это можно решить когда вы десериализовать так вы направьте ссылку на реальный объект. Вам просто нужно сломать ссылку на сериализацию, чтобы иметь возможность сериализовать ее.

обнаружение круговой ссылки в JavaScript может быть сделано путем рекурсивной итерации через все объекты (с for (x in y)), магазин x в массиве и сравните каждый x с оператором идентификации (a.к. a. строгий оператор сравнения) === для каждого z во временном массиве. Всякий раз x === z равно true, заменить ссылку на x с заполнителем, который будет сериализован в вышеупомянутый ref.

альтернативой сохранению массива над "посещенными" объектами является "заражение" объектов, которые вы перебираете, устанавливая на них свойство, как в этом очень наивном примере:

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

    x.visited = true;
}

нет хорошего способа обнаружить циркулярность в объектах, но это возможно, хотя путем ходьбы по дереву объектов и проверки ссылок. Я испек функцию ходьбы по узлам, которая пытается определить, был ли узел уже использован в качестве его родителя

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

ключевым моментом является то, что значение объекта равно другому значению только если это одна и та же ссылка на объект, а не когда это другой объект (даже если он полностью похож).


Это небольшое расширение ответа Андриса, которое говорит вам, где находится первый круговой элемент, чтобы вы могли справиться с ним соответственно.

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

для его родителей.


Я думал о том, что вы пытаетесь выполнить, основываясь на исходном коде из вашего другого вопроса. Почему бы не сделать что-нибудь подобное?

Player = function()
{
    this.UnitTypeXpower = 2
    this.UnitTypeYpower = 7

}

UnitTypeXAdd = function(owner)
{
    owner.UnitTypeXpower++;   
}

таким образом, вам не нужно использовать круговую ссылку, и она выполняет то же самое.


вот код, который я использую для обнаружения циклических ссылок, он использует метод, который был предложен в принятый ответ асбйорну, при котором каждое значение проходит и его ссылка поддерживается в массиве, чтобы следующее значение можно было сравнить с теми, которые ранее прошли.

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

Array.isArray был введен только с Javascript 1.8.5, см. страница MDN

Object.keys был введен только с Javascript 1.8.5, см. страница MDN

также стоит отметить, что arguments.callee устарел и запрещен в строгом режиме в предпочтении к использованию named функции