11

Sto facendo un gioco e mi sono imbattuto in un problema ... Quando provo a salvare, JSON fallisce e segnala che il riferimento circolare viene creato da qualche parte. Non penso che sia in realtà, non riesco a vederlo, quindi c'è un algoritmo o qualcosa che potrebbe dirmi dove è esattamente (tra quali oggetti e cose)? Inoltre, esiste un'alternativa JSON in grado di salvare riferimenti circolari? Sto utilizzando un server node.js, ho visto this, ma non riesco a farlo funzionare (non è stato creato come modulo che posso richiedere() nel mio codice).C'è un modo per testare il riferimento circolare in JavaScript?

risposta

12

Se si desidera serializzare un riferimento circolare per poterlo salvare, è necessario creare il riferimento "virtuale" in quanto non può essere serializzato come riferimento circolare, poiché ciò comporterebbe la serializzazione per serializzare la stessa cerchia di oggetti per sempre (o almeno fino a quando il runtime non ha esaurito la memoria).

Quindi, invece di memorizzare il riferimento circolare stesso, è sufficiente memorizzare un puntatore all'oggetto. Il puntatore sarà qualcosa come ref : '#path.to.object' che può essere risolto quando si deserializza in modo da puntare il riferimento all'oggetto reale. Hai solo bisogno di rompere il riferimento sulla serializzazione per poterlo serializzare.

scoprendo un riferimento circolare in JavaScript può essere fatto ricorsivamente scorrendo tutti gli oggetti (con for (x in y)), magazzini x in un array e confrontare ogni x con l'operatore identità (pseudonimo strict comparison operator) === per ogni z nella matrice temporanea. Ogni volta che x === z è uguale a vero, sostituire il riferimento a x con un segnaposto che verrà serializzato al numero sopra menzionato ref.

Un'alternativa a mantenere una matrice sugli oggetti "visitati" è quello di "Taint" gli oggetti che si scorrere impostando una proprietà su di loro, come in questo esempio molto ingenua:

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

    x.visited = true; 
} 
+3

+1 per contaminare :) – dinjas

8

non c'è alcun bene modo per rilevare la circolarità negli oggetti ma è possibile camminando nell'albero degli oggetti e controllando i riferimenti. Ho sfornato una funzione di nodo-walking che cerca di rilevare se un nodo è stato già utilizzato come suo genitore

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

E l'uso sarebbe isCircularObject(obj_value) in cui la funzione restituisce true se circolarità esiste e se non 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 

Il punto chiave è che un valore oggetto è uguale con un altro valore unicose è lo stesso riferimento oggetto e non quando è un altro oggetto (anche se tutto simile).

+0

Grazie mille! – corazza

1

Stavo pensando a quello che stai cercando di realizzare in base al codice iniziale della tua altra domanda. Perché non fare qualcosa di simile.

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

} 

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

In questo modo non è necessario utilizzare un riferimento circolare e realizza la stessa cosa.

+0

Gli oggetti reali che uso sono molto più complessi, ho messo il potere proprio come un segnaposto, perché mettere l'intero codice sarebbe una cattiva idea ... – corazza

4

Questa è una piccola estensione della risposta di Andris che ti dice dove è il primo elemento circolare in modo che tu possa occuparti di conseguenza.

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

Se non si desidera una stringa, l'array tree non è necessario.Basta cambiare la funzione originale di

return value; 

per l'oggetto circolare stesso o

return parents.pop(); 

per il suo genitore.

1

Ecco il codice che sto usando per rilevare riferimenti circolari, utilizza la tecnica che è stata suggerita nel accepted answer by asbjornu, in cui ogni valore è calpestato e il suo riferimento è mantenuto in una matrice in modo che il valore successivo possa essere confrontato con quelli precedentemente camminati.

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

Questo codice è disponibile sul jsfiddle, dove è possibile testare in prima persona. Ho anche eseguito alcuni test delle prestazioni su jsperf.

Array.indexOf è stato introdotto solo a partire dal Javascript 1.6, vedere MDN page

Array.isArray è stato introdotto solo a partire dal JavaScript 1.8.5, vedere MDN page

Object.keys è stato introdotto solo a partire dal JavaScript 1.8.5, vedere MDN page

Vale anche la pena notare che arguments.callee è deprecato e vietato in modalità rigorosa in preferenza all'utilizzo di named fun cations

+0

Il tuo codice può essere semplificato in questo modo: prova { JSON.stringify (obj); return true; } catch (e) { return false; } –

+0

OP ha scritto: "Sto facendo un gioco, e ho trovato un problema ... Quando provo a salvare, JSON fallisce e segnala che il riferimento circolare viene fatto da qualche parte. Non penso che sia in realtà, non riesco a vederlo, quindi c'è un algoritmo o qualcosa che potrebbe dirmi dove è esattamente (tra quali oggetti e cose)? Inoltre, esiste un'alternativa JSON in grado di salvare riferimenti circolari? Sto eseguendo un server node.js, l'ho visto, ma non riesco a farlo funzionare (non è stato creato come modulo che posso richiedere() nel mio codice). ' – Xotic750

Problemi correlati