Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bind an event function to a class but using removeEventListener and removing his references allowing the garbagecollector to work correctly

As we all know when we create a class in JavaScript a normal function returns the class object but events return the event object and the class object gets lost:

function class(a){
 this.name=a;
 document.addEventListener('click',this.click,false);
 xhr.addEventListener('load',this.xhr,false);
 this.normal()
}
class.prototype={
 click:function(e){
  //e=event,this=theDocument //can't access class
 },
 xhr:function(e){
  //e=event,this=theXHR //can't access class
 },
 normal:function(e){
  //e=null,this=class
 }
}

Whats the best way to bind those events to our class?

By best way I mean no or just a tiny reference, the ability to remove the events in a native way (removeEventListener) and to absolutely not create memory leaks.

  1. to remove an eventlistener you need to pass the function as a reference, so addEventListener('click',function(){alert('something')},false) does not work.

  2. I read references like var that=this inside functions create leaks; true?

Known ways:

function class(a){
 this.name=a;
 var that=this;// reference
 //simply reference the whole object as a variable.
 
 var bindedClick=this.click.bind(this);//bind the click to the class
 //(js 1.85)
 //can i use removeEventListener('click',bindedClick,false) later?
 //apply && call (js 1.3)
}

As I'm not sure if var that=this (as it is the whole object) creates leaks, sometimes I minimize this by saving the info into an array and as reference I use a id:

var class={};
var id='ID'+Date.now();

class[id].info={here i store all the info i need later text only}
//this will be stored also in a cookie / Localstorage to reuse later.

class[id].dom={here i store the dom references}
class[id].events{here i store the xhr events}//if needed
//this are Temp only

and to get the info I just pass the id by adding it to the event element:

class[id].events.xhr.id=id;
class[id].events.xhr.onload=class.f

class.prototype.f=function(e){
 //this.response,class[this.id]<- access everything.
 this.removeEventListener('load',class.f,false);
 delete class[this.id].events.xhr;
 delete this.id
}

class[id].dom.id=id;
class[id].dom.onclick=class.f2

class.prototype.f2=function(e){
 //class[e.target.id],class[this.id]<- access everything.
 this.removeEventListener('click',class.f2,false);
 delete class[this.id].dom;
 delete this.id
}

As you can see in this example above I have access to everything and the reference is just a small string.

I store the DOM because I define the DOM references on load so I don't have to call getElementById() every time.

I store the events like XHR in the class as I want to be able to call xhr.abort() from outside, and also able to call removeEventListener.

I need to minimize the impact to the memory but at the other side I need to control many elements that have multiple simultaneous events to control the garbage collector "manually" by removing all events and references.

To make sure you understand that the problem is bigger than it looks: it's a download manager for Chrome. Input field for download URL:

  1. xhr to get the fileinfo (size, name, acceptranges, mime)
  2. store info in localstorage and cached array
  3. create visual DOM elements to show progress (event:progress, click, mouseover..)
  4. xhr request a chunk 0-1000 (event:load)
  5. onprogress display progress data (event:progress,error)
  6. request filesystem(event:ok,error)
  7. readfile/check file (event:ok,error)
  8. request filewriter (event:ok,error)
  9. append to file (events=ok,error)
  10. on ok start again from 3. until file is finished
  11. when finished I delete all the references / events / extra info //this to help the garbage collector.
  12. change the DOM contents.

Every file has sooo many events every sec.

Which of these 3 is the best solution or are there any better solutions?

bind();//or call / apply

var that=this; //reference to the whole object

var id=uniqueid; // reference to the object's id

BASED ON the answers:

(function(W){
 var D,dls=[];
 function init(){
  D=W.document;
  dls.push(new dl('url1'));
  dls.push(new dl('url2'));
 }
 function dl(a){
  this.MyUrl=a;
  var that=this;
  var btn=D.createElement('button');
  btn.addEventListener('click',this.clc,false);
  D.body.appendChild(btn);
 }
 dl.prototype={
  clc:function(e){
    console.log(that)
  }
 }
 W.addEventListener('load',init,false);
})(window)

var that=this does not work.

This works; but I need a lot of checks, switch if and execute multiple functions.

(function(W){
 var D,dls=[];
 function init(){
  D=W.document;
  dls.push(new dl('url1'));
  dls.push(new dl('url2'));
 }
 function dl(a){
  this.MyUrl=a;
  this.btn=D.createElement('button');
  btn.addEventListener('click',this,false);
  D.body.appendChild(btn);
 }
 dl.prototype={
  handleEvent:function(e){
   e.target.removeEventListener('click',this,false);//does this the work?
   return this.clc(e);
  },
  clc:function(e){
   console.log(this,e)
  }
 }
 W.addEventListener('load',init,false);
})(window)

BIND :

(function(W){
 var D,dls=[];
 function init(){
  D=W.document;
  dls.push(new dl('url1'));
  dls.push(new dl('url2'));
 }
 function dl(a){
  this.MyUrl=a;
  this.clcB=this.clc.bind(this);
  this.btn=D.createElement('button');
  this.btn.addEventListener('click',this.clcB,false);
  D.body.appendChild(this.btn);
 }
 dl.prototype={
  clc:function(e){
   e.target.removeEventListener('click',this.clcB,false);//does this the work?
   delete this.clcB;
   console.log(this)
  }
 }
 W.addEventListener('load',init,false);
})(window)
like image 274
cocco Avatar asked Mar 23 '23 18:03

cocco


2 Answers

Better solution is to have your "class" implement the EventListener interface.

You do this by adding a handleEvent method to MyClass.prototype. This allows you to pass the object directly to .addEventListener() instead of passing a handler.

When an event occurs, the handleEvent() method will be invoked, with your object as the this value. This allows you to have access to all the properties/methods of the object.

function MyClass(a) {
    this.name = a;

    // pass the object instead of a function
    document.addEventListener('click', this, false);
    xhr.addEventListener('load', this, false); // where did `xhr` come from?

    this.normal()
}

MyClass.prototype = {

    // Implement the interface
    handleEvent: function(e) {
        // `this` is your object
        // verify that there's a handler for the event type, and invoke it
        return this[e.type] && this[e.type](e);
    },

    click: function (e) {
        // `this` is your object
    },
    load: function (e) {
        // `this` is your object
    },
    normal: function (e) {
        // `this` is your object
    }
}

Notice that I changed the name of your xhr method to load. This makes it easier to call the proper method based on the event type.

Then when it comes time to call .removeEventListener(), just do it like normal from the element, but again pass the object instead of the handler.

like image 146
user2437417 Avatar answered Apr 25 '23 14:04

user2437417


I read references like var that=this inside functions create leaks

Wrong. They create references that are not garbage-collected until the function is, but that's exactly what you want. It's not a leak.

It might cause problems in very old browsers (IE6) that cannot handle cyclic references, but simply don't care about those. Also, by calling removeEventListener you even destroy that cyclic reference so everything is fine.

I minimize this by saving the info into an array and the reference is just a small string...

No. The reference is your class array which will much more likely create a leak if you forget to delete the ids from id. Don't overcomplicate this.

Which of these 3 is the best solution or are there any better solutions?

var that=this; //reference to the whole object

The standard approach. Very fine.

.bind();

Can be more concise than a that variable, and has the same reference layout (no difference in garbage collection). Notice that a native bind is not available on old browsers, so some people frown upon this method. Fine as well, but might need a shim.

var id=uniqueid; // reference to the object's id

Don't do that. It's too complicated, and you easily make mistakes that really lead to huge leaks.

The event listener interface with a handleEvent method (presented by @CrazyTrain)

Very elegant, but unknown to the most people. Low memory footprint since no bound, privileged functions need to be created. Works very well for classes that need to handle only one event type, but needs some kind of delegation when supporting different events or different targets with the same listener instance (and can become more complex than the other approaches). Contra: The handlerEvent method is public and everything that has access to your instance can "fire" (spoof) events.


var that=this does not work.

You're using it wrong. The point of this approach is to create a closure in which new functions have access to the that variable. The closure scope is your constructor, you cannot access it from the prototype.

var that=this;
btn.addEventListener('click',function(e){that.clc(e);},false);

// use `this` in the prototype

handleEvent works... but i need alot of checks , swich if and execute multiple functions.

No. Your instances do only handle the click of the button, so this approach is fine for you. You even could put all of your code from clc directly into handleEvent.

like image 31
Bergi Avatar answered Apr 25 '23 16:04

Bergi