[譯]JavaScript:循環對象檢測


原文:http://blog.vjeux.com/2011/javascript/cyclic-object-detection.html


包含循環結構的對象稱之為循環對象,循環對象無法遍歷,因為在遍歷過程中會產生死循環.本文講了三種用來檢測一個對象是否循環對象的技術.

譯者注:創建循環對象

作者沒有講怎么創建一個循環對象,我覺的有必要講一下.循環對象是一個自身的某個屬性指向自己的對象.可以這樣來創建.

var foo = {};
foo["bar"] = foo;
jQuery.param(foo);  //這是一個死循環,瀏覽器報錯InternalError: too much recursion

包含一個循環對象的對象也是循環對象

var obj = {key:foo}
jQuery.param(obj);  //InternalError: too much recursion

還有一種Mozilla的私有技術可以創建循環對象,叫井號變量,不過從Firefox12起,已經廢棄

var foo = #1= {bar: #1#} 
jQuery.param(a);            //InternalError: too much recursion

你肯定用過循環對象,因為:

window.window.window.window.window.window.window === window

給對象的每個屬性加標記

想要檢測一個對象內部是否包含了循環結構,最先想到的方法就是給每個節點添加標記.在遍歷過程中,如果我們遇到一個已經被標記過的節點,也就說明該對象包含了循環結構.

這種方法修改了一個不屬於我們的對象.這是很危險的,會有很多其他的影響.

  • 使用什么來作為唯一的不與現有屬性重復的標記鍵?我用了Math.random,但是這樣仍然有可能和對象已有的屬性重復,不僅會導致錯誤的判斷結果,還會讓該屬性被誤刪除!
  • 添加一個新的屬性,然后再刪除它,這樣會反復改變對象的內存占用,很可能導致內存拷貝(memory copy)以及內存空洞(memory holes).
  • 該方法不能處理Sealed objectsProxies.(譯者注:因為無法添加屬性)
function isCyclic (obj) {
  var seenObjects = [];
  var mark = String(Math.random());
 
  function detect (obj) {
    if (typeof obj === 'object') {
      if (mark in obj) {
        return false;
      }
      obj[mark] = true;
      seenObjects.push(obj);
      for (var key in obj) {
        if (obj.hasOwnProperty(key) && !detect(obj[key])) {
          return false;
        }
      }
    }
    return true;
  }
 
  var result = detect(obj);
 
  for (var i = 0; i < seenObjects.length; ++i) {
    delete seenObjects[i][mark];
  }
 
  return result;
}

把標記存儲在另外一個獨立的數據結構中

顯然,我們應該避免編輯原對象,但該怎么避免呢?我想到的辦法是使用一個數組把訪問過的節點存儲下來.然后使用indexOf方法,判斷我們是否訪問過這個節點,可以,這是一個O(n²)的復雜度,而上面的方法是O(n).

function isCyclic (obj) {
  var seenObjects = [];
 
  function detect (obj) {
    if (typeof obj === 'object') {
      if (seenObjects.indexOf(obj) !== -1) {
        return true;
      }
      seenObjects.push(obj);
      for (var key in obj) {
        if (obj.hasOwnProperty(key) && detect(obj[key])) {
          return true;
        }
      }
    }
    return false;
  }
 
  return detect(obj);
}

underscore.js使用了這種方法來檢測循環對象.

利用原生的JSON.stringify

最后一種方法有點技巧性.如果瀏覽器支持ES5中的JSON對象.JSON.stringify會在處理循環對象時拋出異常.

function isCyclic(obj) {
    var isNativeJSON = typeof JSON !== 'undefined' && JSON.stringify.toString().match(/\n\s*\[native code\]\s*\n/);
    if (!isNativeJSON) {
        throw 'Native JSON.stringify is not available, can\'t use this technique.';
    }
    try {
        JSON.stringify(obj);
        return false;
    } catch (e) {
        return true;
    }
}

總結

得出的結論有點讓人沮喪,因為沒有一種技術能完全滿足我們.為了能夠寫出一種完美的進行循環對象檢測的代碼,我們需要一種哈希表結構的對象,但目前還沒有(譯者注:我們現在有Map了).

如果你感興趣的話,上面的這些源代碼都存儲在github上,另外還有用來比較這三種技術性能的jsperf.

如果你需要存儲或加載某個循環結構,可以使用Douglas Crockford的 decycle & retrocycle functions.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM