11

我正在製作一款遊戲,並且遇到了一個問題......當我嘗試保存時,JSON失敗並報告正在製作循環引用。我不認爲它是真的,我看不到它,所以有算法或任何可以告訴我它究竟在哪裏(在哪些對象和東西之間)的算法?另外,有沒有可以保存循環引用的JSON替代方案?我正在運行一個node.js服務器,我看到了this,但是我無法讓它工作(它不是作爲一個模塊,我可以在我的代碼中需要())。有沒有辦法在JavaScript中測試循環引用?

回答

12

如果您想要序列化一個循環引用以便保存它,則需要將引用設置爲「虛擬」,因爲它不能被序列化爲循環引用,因爲這會導致序列化來序列化同一個圓永遠的對象(或者至少在運行時耗盡內存之前)。

因此,不是存儲循環引用本身,而是存儲一個指向該對象的指針。這個指針就像ref : '#path.to.object'這樣的東西,當你反序列化時可以解決這個問題,所以你將引用指向實際的對象。你只需要打破序列化的參考,以便能夠序列化它。

發現在JavaScript中循環引用可以通過在陣列中的所有對象(用for (x in y)),商店x與恆等算子(也稱爲strict comparison operator===臨時數組中遞歸地迭代和比較每個x每個z來完成。每當x === z等於true時,用佔位符替換引用x,該佔位符將被序列化到上述ref

,以保持一個數組在「參觀」的對象另一種方法是「污點」的你在這個非常簡單的例子對它們設置屬性,就像遍歷對象:

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

    x.visited = true; 
} 
+3

+1污染:) – dinjas

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)其中函數,如果圓存在,false如果沒有返回true一個節點行走功能。

// 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 

的關鍵點是一個對象的值是具有其他值僅等於如果它是同一個對象引用,而不是當它的另一對象(即使完全相似)。

+0

非常感謝! – corazza

1

我正在考慮你想要完成什麼,基於你的另一個問題的初始代碼。爲什麼不做這樣的事情。

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

} 

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

這樣,你不必使用循環引用,它可以完成同樣的事情。

+0

我使用的實際對象要複雜得多,我把它放在一個佔位符中,因爲把整個代碼放在那裏是一個壞主意...... – corazza

4

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

其父。

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在嚴格模式下被棄用和禁止,優先使用命名的樂趣ctions

+0

您的代碼可以這樣簡化:try { JSON.stringify( OBJ); 返回true; (e){ return false; } catch(e){ return false; } –

+0

OP寫道:「我正在製作一款遊戲,並且遇到了一個問題......當我嘗試保存時,JSON失敗並報告說正在製作循環引用。我不認爲它是真的,我看不到它,所以有算法或任何可以告訴我它究竟在哪裏(在哪些對象和東西之間)的算法?另外,有沒有可以保存循環引用的JSON替代方案?我正在運行一個node.js服務器,我看到了這個,但是我無法實現它的功能(它不是作爲一個模塊,我可以在我的代碼中需要())。 ' – Xotic750

相關問題