title: Javascript closure : Memory Leak
body: <p>I have a memory leak I don't understand. I programmed a mechanism to handle event with semi-automatically unbinding which should allow me to clean up memory easily.
  But in one case, the clean up doesn't happen (I use chrome's "profile (memory heap)" to check for instances of "EventHandler" left). I really don't get why it happen. There's something weird with the closure...</p>
  
  <p><a href="http://serge.snakeman.be/Demo/test.html" rel="nofollow">see it in action with chrome</a></p>
  
  <pre><code>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 &amp;&amp; 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) &amp;&amp; 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"); });
  </code></pre>