2013-04-07 214 views
6

我有內存泄漏,我不明白。我編寫了一個機制來處理半自動解除綁定的事件,這可以讓我輕鬆地清理內存。 但在一種情況下,清理不會發生(我使用chrome的「配置文件(內存堆)」來檢查「EventHandler」左側的實例)。我真的不明白爲什麼會發生。有一些奇怪的與封閉...Javascript關閉:內存泄漏

see it in action with chrome

function Bind(obj, f) { 
    return function() { 
     return f.apply(obj, arguments); 
    } 
} 

function EventHandler() { 
    this.listeners = new Object(); 

    var _listenerID = 0; 
    this.addListener = function(e, obj, listener, specialDisplay) { 
     if (typeof(listener) === "function") { 
      var listenerID = ++_listenerID; 
      console.log("Events (" + (++EventHandler.All) + ", " + listenerID + ") ++" + e); 

      if (!this.listeners.hasOwnProperty(e)) { 
       this.listeners[e] = new Object(); 
      } 
      this.listeners[e][listenerID] = listener; 

      if (obj != null && typeof(obj.removeListener) == "function") { 
       var deleteListenerID = obj.addListener("Delete", null, Bind(this, function() { 
        this.removeListener(e, listenerID); 
        obj.removeListener("Delete", deleteListenerID); 
       })); 
      } 

      return listenerID; 
     } 

     return null; 
    } 
    this.fire = function(e, obj) { 
     if (this.listeners.hasOwnProperty(e)) { 
      for(var i in this.listeners[e]) { 
       this.listeners[e][i](obj); 
      } 
     } 
    } 
    this.removeListener = function(e, listenerID) { 
     if (this.listeners.hasOwnProperty(e) && this.listeners[e].hasOwnProperty(listenerID)) { 
      delete this.listeners[e][listenerID]; 

      console.log("Events (" + (--EventHandler.All) + ", " + listenerID + ") --" + e); 
     } 
    } 
} 

EventHandler.All = 0; 

function Loader() { 
} 

Loader.files = new Object(); 

Loader.LoadImage = function(src, f) { 
    if (!Loader.files.hasOwnProperty(src)) { 
     var handler = new EventHandler(); 

     console.log("Loading.... (" + src + ")"); 

     Loader.files[src] = function(fnct) { 
      handler.addListener("ImageLoaded", handler, function(img) { 
       fnct(img); 
      }); 
     } 

     handler.addListener("ImageLoaded", handler, function() { 
      Loader.files[src] = function(fnct) { 
       fnct(img); 
      } 
     });  

     var img = new Image(); 
     $(img).load(function() { 
      console.log("Loaded.... (" + src + ")"); 
      handler.fire("ImageLoaded", img); 
      handler.fire("Delete"); 
      $(img).unbind('load'); 
     }); 
     img.src = src; 
    } 

    Loader.files[src](f); 
} 

Loader.LoadImage("http://serge.snakeman.be/Demo/house.jpg", function() { alert("ok"); }); 
+0

你可以請做,因爲錯誤消息說,並粘貼代碼從jsfiddle的問題。謝謝。 – JJJ 2013-04-07 15:24:03

+0

我真的不知道代碼的哪一部分會更有意義,實際上我對stackoverflow警告有點不滿。 – Serge 2013-04-07 15:31:19

+0

如果有人可以upvote我的問題,我會給予50賞金的答案... – Serge 2013-04-14 15:14:53

回答

2

創建持有經handler變量的引用到EventHandler實例關閉。其中一個封閉的保持圖像加載後:

handler.addListener("ImageLoaded", handler, function() { 
     Loader.files[src] = function(fnct) { 
      fnct(img); 
     } 
    });  

它的內部函數function(fnct) {...。只要閉包存在,EventHandler的實例就不能被釋放。你唯一的解決方案是擺脫封閉。或者如果可能的話,手動釋放實例。下面會爲你工作:

handler.fire("Delete"); 
handler = undefined; 

Chrome的內存分析器會顯示該對象的保留樹,這只是另一種說法:「誰持有的引用對象」。在你的例子是事件處理器 < - 處理程序(如併入由封閉所述的LoadImage方法的變量)< - house.jpg,這實際上是Loader.files[src]和具有值function(fnct) { fnct(img); }

+0

當圖像被加載時,參考保持在Loader中。由於我將它設置爲另一個值,文件[src]被刪除。 Loader.files [src] = function(fnct)fnct(img); } – Serge 2013-04-22 10:23:23

+0

你是對的,當然,我錯過了。但是這並沒有太大的改變:這個函數阻止了EventHandler實例被釋放(稱爲分析器)。我不是100%確定的,但只要函數存在,就可以引用處理程序變量,EventHandler實例將不會被釋放。 – zeroflagL 2013-04-22 11:29:36

+0

對於不存在的函數,你建議做什麼?無論如何,我無法得到它可以持有的地方。 :S – Serge 2013-04-22 11:33:28

2

在添加「偵聽器」時,如果長時間使用查詢,請確保刪除它。

this.listeners = new Object(); 

this.listeners[e] = new Object(); 

這將到收聽者添加對象作爲數組,但沒有在任何點移除它們。

這可能是內存消耗的原因。它可能不會泄漏,它的對象分配。使用瀏覽器消耗你的RAM。 :)

+0

我正在刪除它們。在控制檯上,您可以看到「Events(0,4)--Delete」,表示最後一個剩餘的監聽器已被刪除。 – Serge 2013-04-23 07:08:01

+0

當您在系統中處理偵聽器時的一般做法。 您需要處理它們,包括刪除它們。在某些情況下,這同樣適用於瀏覽器:)如果你刪除它們,那麼它就不在我的範圍之內。但爲了進行雙重檢查,請檢查控制檯'listeners []'中的元素。是否有元素。 – MarmiK 2013-04-23 07:30:45

+0

將對象添加到控制檯,使chrome擁有另一個引用。但是因爲你只是在談論聽衆[]是不應該傷害嘗試(我今天晚上會嘗試)。 – Serge 2013-04-23 07:59:03