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
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;
}
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).
Grazie mille! – corazza
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.
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
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.
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
Il tuo codice può essere semplificato in questo modo: prova { JSON.stringify (obj); return true; } catch (e) { return false; } –
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
+1 per contaminare :) – dinjas