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.
to remove an eventlistener you need to pass the function as a reference, so addEventListener('click',function(){alert('something')},false)
does not work.
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:
xhr
to get the fileinfo (size, name, acceptranges, mime)xhr
request a chunk 0-1000 (event:load)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)
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With