Do events handlers on a DOM node get deleted with the node? - javascript

(Note: I'm using jQuery below, but the question is really a general JavaScript one.)
Say I've got a div#formsection whose contents are repeatedly updated using AJAX, like this:
var formSection = $('div#formsection');
var newContents = $.get(/* URL for next section */);
formSection.html(newContents);
Whenever I update this div, I trigger a custom event, which binds event handlers to some of the newly-added elements, like this:
// When the first section of the form is loaded, this runs...
formSection.find('select#phonenumber').change(function(){/* stuff */});
...
// ... when the second section of the form is loaded, this runs...
formSection.find('input#foo').focus(function(){/* stuff */});
So: I'm binding event handlers to some DOM nodes, then later, deleting those DOM nodes and inserting new ones (html() does that) and binding event handlers to the new DOM nodes.
Are my event handlers deleted along with the DOM nodes they're bound to? In other words, as I load new sections, are lots of useless event handlers piling up in the browser memory, waiting for events on DOM nodes that no longer exist, or are they cleared out when their DOM nodes are deleted?
Bonus question: how can test this myself?

Event handler functions are subject to the same Garbage Collection that other variables are. That means they will be removed from memory when the interpreter determines that there is no possible means to obtain a reference to the function. Simply deleting a node however does not guarantee garbage collection. For instance, take this node and associated event handler
var node = document.getElementById('test');
node.onclick = function() { alert('hai') };
Now lets remove the node from the DOM
node.parentNode.removeChild(node);
So node will no longer be visible on your website, but it clearly still exists in memory, as does the event handler
node.onclick(); //alerts hai
As long as the reference to node is still accessible somehow, it's associated properties (of which onclick is one) will remain intact.
Now let's try it without creating a dangling variable
document.getElementById('test').onclick = function() { alert('hai'); }
document.getElementById('test').parentNode.removeChild(document.getElementById('test'));
In this case, there seems to be no further way to access the DOM node #test, so when a garbage collection cycle is run, the onclick handler should be removed from memory.
But this is a very simple case. Javascript's use of closures can greatly complicate the determination of garbage collectability. Lets try binding a slightly more complex event handler function to onclick
document.getElementById('test').onclick = function() {
var i = 0;
setInterval(function() {
console.log(i++);
}, 1000);
this.parentNode.removeChild(this);
};
So when you click on #test, the element will instantly be removed, however one second later, and every second afterwards, you will see an incremented number printed to your console. The node is removed, and no further reference to it is possible, yet it seems parts of it remain. In this case the event handler function itself is likely not retained in memory but the scope it created is.
So the answer I guess is; it depends. If there are dangling, accessible references to deleted DOM nodes, their associated event handlers will still reside in memory, along with the rest of their properties. Even if this is not the case, the scope created by the event handler functions might still be in use and in memory.
In most cases (and happily ignoring IE6) it is best to just trust the Garbage Collector to do its job, Javascript is not C after all. However, in cases like the last example, it is important to write destructor functions of some sort to implicitly shut down functionality.

jQuery goes to great lengths to avoid memory leaks when removing elements from the DOM. As long as you're using jQuery to delete DOM nodes, removal of event handlers and extra data should be handled by jQuery. I would highly recommend reading John Resig's Secrets of a JavaScript Ninja as he goes into great detail on potential leaks in different browsers and how JavaScript libraries like jQuery get around these issues. If you're not using jQuery, you definitely have to worry about leaking memory through orphaned event handlers when deleting DOM nodes.

You may need to remove those event handlers.
Javascript memory leaks after unloading a web page
In our code, which is not based on jQuery, but some prototype deviant, we have initializers and destructors in our classes. We found it's absolutely essential to remove event handlers from DOM objects when we destroy not only our application but also individual widgets during runtime.
Otherwise we end up with memory leaks in IE.
It's surprisingly easy to get memory leaks in IE - even when we unload the page, we must be sure the application "shuts down" cleanly, tidying away everything - or the IE process will grow over time.
Edit: To do this properly we have an event observer on window for the unload event. When that event comes, our chain of destructors is called to properly clean up every object.
And some sample code:
/**
* #constructs
*/
initialize: function () {
// call superclass
MyCompany.Control.prototype.initialize.apply(this, arguments);
this.register(MyCompany.Events.ID_CHANGED, this.onIdChanged);
this.register(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);
},
destroy: function () {
if (this.overMap) {
this.overMap.destroy();
}
this.unregister(MyCompany.Events.ID_CHANGED, this.onIdChanged);
this.unregister(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);
// call superclass
MyCompany.Control.prototype.destroy.apply(this, arguments);
},

Not necessarily
The documentation on jQuery's empty() method both answers my question and gives me a solution to my problem. It says:
To avoid memory leaks, jQuery removes
other constructs such as data and
event handlers from the child elements
before removing the elements
themselves.
So: 1) if we didn't do this explicitly, we'd get memory leaks, and 2) by using empty(), I can avoid this.
Therefore, I should do this:
formSection.empty();
formSection.html(newContents);
It's still not clear to me whether .html() would take care of this by itself, but one extra line to be sure doesn't bother me.

I wanted to know myself so after a little test, I think the answer is yes.
removeEvent is called when you .remove() something from the DOM.
If you want see it yourself you can try this and follow the code by setting a breakpoint. (I was using jquery 1.8.1)
Add a new div first:
$('body').append('<div id="test"></div>')
Check $.cache to make sure there is no events attached to it. (it should be the last object)
Attach a click event to it:
$('#test').on('click',function(e) {console.log("clicked")});
Test it and see a new object in $.cache:
$('#test').click()
Remove it and you can see the object in $.cache is gone as well:
$('#test').remove()

Related

jQuery: better performance with check whether binding element exists?

I wonder... Let's imagine I have a code something like that:
$('#specific-element').change(some_long_and_ajax_function);
Element with binded ID doesn't exist on all of my pages. On some of them only. I do check whether this element exists like this:
if($('#specific-element').length > 0){
$('#specific-element').change(some_long_and_ajax_function);
// There can be more stuff related to this specific element
}
My question: is it worth it? Is there any performance impact for binding handlers for non-existing elements or checking length is worse than it? Or is it basically same and I have two useless rows? What would you recommend? The first one keeps code nice and clear but I'm not sure if this will be "healthy" for jQuery with dozens of examples like that. Thanks.
jQuery fails gracefully if the element doesn't exist, internally it does it's own check to see if the element exists, and if it doesn't the event handler isn't attached etc.
jQuery() calls jQuery.fn.init wich checks if the passed argument is a string, which in your case it is, it then calls jQuery.fn.find with the right context, and inside that it does
var i, ret = [],
self = this,
len = self.length;
... code
for (i = 0; i < len; i++) {
// add event handlers
}
so the loop never runs if there are no elements to run it on, so there's no need to do your own check, just let jQuery handle it.
EDIT:
When you call $('#specific-element').length you're already calling $(), and it does all the usual things internally and returns an array-like jQuery object that has a length property, and that's exactly what $('#specific-element').change... does as well, and jQuery's on() also returns rather quickly if no elements exists in the collection, so there really is no difference.
If you're really concerned about speed, you'd do something like
var el = document.getElementById('specific-element');
if ( el !== null ) {
el.addEventListener('change', fn, false);
}
but there's really no reason, just add event handler with jQuery the usual way, without checking it the element exists, it's what almost every website in existance does, and it works just fine.
As said in the comment, jQuery check if the element exist before binding events. But there is a speed difference wether you check or not.
When checking before binding, you save time if the element doesn't exist because getting a property (.length) is way faster than calling a function (which will probably call other functions) and failing gracefully.
But if the element exist, it will be slower since you add a condition before binding. Hence, it add 1 more step than if you did not check before and directly binded the event.
Just interpret those test results : http://jsperf.com/check-before-binding1
You can see that if the element exist, the difference between the check before is only 4000 operations / second more. It is not a lot... On the other hand, when you check if the element exist and it doesn't, it save 1,000,000 operations / second because it doesn't call the function .change().
Conclusion
I'd say checking before is better if that really matter, but mostly, it doesn't. If the element is most often present than missing on different pages, i'd directly bind the event. If the element is mostly missing than present, i'd check before binding.
In the end, we are talking about 0.0000001ms...
1I have slightly changed to code to optimise your. Caching the element sure is important is you want better performances.

Is there actually a good reason for jQuery to manipulate 'this' keyword in event handlers?

Given the following, common scenario:
console.log(this); // window or any parent object
$('.selector').on('click', function(event) {
console.log(this); // clicked DOM element
});
var myFunc = function() {
console.log(this); // window or parent object
}
Since version 1.3 jQuery adds the event.currentTarget when binding event handlers for which counts event.currentTarget === this, so is there actually a good reason to manipulate this and switch context? Doesn't this behaviour generally go against the unspoken rule of "don't change keyword values" (like undefined = 'not defined')?
This "feature" of jQuery makes a lot of OOP less efficient and awkward imho, when we need to either cache the original this in a variable like self or use helpers like jQuery.proxy to reassign context to event handlers.
My question: is this just a relic of early jQuery implementations kept alive or is there an actual benefit which I cannot see (except maybe the slightly more convenient way than accessing event.currentTarget to get the element...)?
Let's say you've got an object with some methods on it:
var object = {
click: function() {
alert(this.property);
},
property: "Hello World"
}
You can call object.click() and, as you'd expect, you'll get "Hello World" in the alert.
You'd like to be able to use that "click" function as an event handler:
$("button").on("click", object.click);
However you discover that that doesn't work, because jQuery invokes the "click" function with this set to the DOM node for the clicked button. This is irritating. It's also inevitable because of the semantics of JavaScript function calls.
When you call the "click" function by way of a property reference, the language arranges for this to refer to that object. That's why object.click() works. However, when you fetch the reference to the function and pass it across a function boundary (as in the call to .on()), that relationship is lost. All that the jQuery method gets is a plain unadorned function that has absolutely no inherent relationship to the original object involved in its definition.
Thus, jQuery really has only two choices. The first is that it could make explicit the fact that the function is unconnected by arranging for this to be undefined. That wouldn't be very useful however. The other choice is to pick something interesting for this, and that's what the library does. Note that the native DOM level 0 event dispatch mechanism does the same thing.
The reason is that jQuery wants to mimic how regular event handlers (ones created without jQuery or any other library) works. In regular event handlers the value of this refers to the DOM node that triggers the event if there is one.
One could in fact consider that this is an example of jQuery not manipulating built-in behavior.

If a DOM Element is removed, are its listeners also removed from memory?

If a DOM Element is removed, are its listeners removed from memory too?
Modern browsers
Plain JavaScript
If a DOM element which is removed is reference-free (no references pointing to it) then yes - the element itself is picked up by the garbage collector as well as any event handlers/listeners associated with it.
var a = document.createElement('div');
var b = document.createElement('p');
// Add event listeners to b etc...
a.appendChild(b);
a.removeChild(b);
b = null;
// A reference to 'b' no longer exists
// Therefore the element and any event listeners attached to it are removed.
However; if there are references that still point to said element, the element and its event listeners are retained in memory.
var a = document.createElement('div');
var b = document.createElement('p');
// Add event listeners to b etc...
a.appendChild(b);
a.removeChild(b);
// A reference to 'b' still exists
// Therefore the element and any associated event listeners are still retained.
jQuery
It would be fair to assume that the relevant methods in jQuery (such as remove()) would function in the exact same way (considering remove() was written using removeChild() for example).
However, this isn't true; the jQuery library actually has an internal method (which is undocumented and in theory could be changed at any time) called cleanData() (here is what this method looks like) which automatically cleans up all the data/events associated with an element upon removal from the DOM (be this via. remove(), empty(), html("") etc).
Older browsers
Older browsers - specifically older versions of IE - are known to have memory leak issues due to event listeners keeping hold of references to the elements they were attached to.
If you want a more in-depth explanation of the causes, patterns and solutions used to fix legacy IE version memory leaks, I fully recommend you read this MSDN article on Understanding and Solving Internet Explorer Leak Patterns.
A few more articles relevant to this:
JScript memory leaks
Memory leaks in IE8
JavaScript Memory Leaks
Manually removing the listeners yourself would probably be a good habit to get into in this case (only if the memory is that vital to your application and you are actually targeting such browsers).
regarding jQuery:
the .remove() method takes elements out of the
DOM. Use .remove() when you want to remove the element itself, as well
as everything inside it. In addition to the elements themselves, all
bound events and jQuery data associated with the elements are removed.
To remove the elements without removing data and events, use .detach()
instead.
Reference: http://api.jquery.com/remove/
jQuery v1.8.2 .remove() source code:
remove: function( selector, keepData ) {
var elem,
i = 0;
for ( ; (elem = this[i]) != null; i++ ) {
if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
if ( !keepData && elem.nodeType === 1 ) {
jQuery.cleanData( elem.getElementsByTagName("*") );
jQuery.cleanData( [ elem ] );
}
if ( elem.parentNode ) {
elem.parentNode.removeChild( elem );
}
}
}
return this;
}
apparently jQuery uses node.removeChild()
According to this : https://developer.mozilla.org/en-US/docs/DOM/Node.removeChild ,
The removed child node still exists in memory, but is no longer part of the DOM. You may reuse the removed node later in your code, via the oldChild object reference.
ie event listeners might get removed, but node still exists in memory.
Don't hesitate to watch heap to see memory leaks in event handlers keeping a reference to the element with a closure and the element keeping a reference to the event handler.
Garbage collector do not like circular references.
Usual memory leak case:
admit an object has a ref to an element. That element has a ref to the handler. And the handler has a ref to the object.
The object has refs to a lot of other objects. This object was part of a collection you think you have thrown away by unreferencing it from your collection.
=> the whole object and all it refers will remain in memory till page exit.
=> you have to think about a complete killing method for your object class or trust a mvc framework for example.
Moreover, don't hesitate to use the Retaining tree part of Chrome dev tools.
Just extending other answers...
Delegated events handlers will not be removed upon element removal.
$('body').on('click', '#someEl', function (event){
console.log(event);
});
$('#someEL').remove(); // removing the element from DOM
Now check:
$._data(document.body, 'events');
Regarding jQuery, the following common methods will also remove other constructs such as data and event handlers:
remove()
In addition to the elements themselves, all bound events and jQuery data associated with the elements are removed.
empty()
To avoid memory leaks, jQuery removes other constructs such as data and event handlers from the child elements before removing the elements themselves.
html()
Additionally, jQuery removes other constructs such as data and event handlers from child elements before replacing those elements with the new content.
Yes, the garbage collector will remove them as well. Might not always be the case with legacy browsers though.

jQuery performance: $('#selector').live() vs manually bind (when working with ajax requests)

When working with content loaded asynchronously is there any difference from a performance point of view between:
// .live()
$('#mybutton').live('click', function(e){ doSomething(); });
and manually bind() the events we need every time after the content has been loaded:
// manual bind every time
$.ajax({
url: url,
success: function(data){
mycontainer.html(data); // data contains #mybutton
$('#mybutton').click(function(e){ doSomething(); });
}
});
?
There are different costs, let's look at them:
$('#mybutton').live('click', function(e){ doSomething(); });
There are 2 main costs here:
The #mybutton selector needs to run immediately for no reason (the result is thrown away, we just wanted the selector anyway...we're binding to document). In this case it's an #id selector so that's a very low cost...in other cases it's not cheap and very wasteful (for example [attr=something]).
Every click that bubbles up to document now has to be checked against this selector, a per-click evaluation cost, this varies with the number of clicks you expect.
Now let's look at the other method:
$('#mybutton').click(function(e){ doSomething(); });
There are 2 main costs here as well:
The #mybutton selector runs, but only once per ajax request. However, we're not wasting it, we're using the results.
The click handler is bound to an actual element, rather than document, so there's a binding cost each time it runs, rather than once
However, there's no per-click cost and the selector call itself isn't wasted...so it's better overall, since you're using an ID, this isn't true in other cases.
In your case, since you're dealing with an ID (and guaranteed a single element), this is much cheaper:
$('#mybutton').click(function(e){ doSomething(); });
In other cases, where you're binding hundreds of elements, .live() is the clear winner, though .delegate() would be even better.
Probably a little, but I wouldn't worry about it. To me the .live() method looks much easier to maintain, so I would use that. As long as nothing's going painfully slow there's no need to worry about performance in JavaScript.
From the looks of your success function, you're attaching an event because that element is now available in your html? Is that so?
If that is the case, then if the function called via the click is always the same then you can use 'live'. Live lets you bind to events that don't yet exist. So you can put this in even before your document.ready. Then as the ajax updates your main document, that event should always work. You won't need to assign it every time.
So you get the performance benefit of not having to do something every time you return from an ajax call, you do the setup without relying on document.ready and its guaranteed to work.
HTH.

Are jQuery events automatically unbound upon destruction of what they where bound to?

If I have the following code in two functions of an object:
add: function()
{
// create trip.
var trip = new Trip();
// add the trip using its id.
this.trips[trip.id] = trip;
},
remove: function(tripId)
{
// remove trip.
delete this.trips[tripId];
}
NOTE: The constructor for the Trip object binds a bunch of custom jQuery event handlers to itself.
Will the event handlers bound to the Trip object be automatically destroyed/cleaned up when the Trip object is deleted?
Would the same occur for a DOM node if it was removed and had event handlers bound to it?
Also I read that objects are not cleaned up by the garbage collector until all references to them no longer exist, so do the event handlers bound to the object by itself count as references and prevent the object from being cleaned up, even when I am no longer referencing it?
The event will not be deleted as jQuery maintains a central repository of all bound event handlers, and does know if or when you deleted an associated object using delete. Try this little test to confirm. (jQuery 1.4.2 only)
jsfiddle link:
// 1. a regular JS object
var root = {};
// Since we can't delete anything created with var (except on Chrome),
// we use an object property here. An array value works just as well,
// which is already the case in your example.
root.o = {};
// 2. at this point, jQuery creates an internal property
// jQuery<UNIQ_ID>, for example jQuery1277242840125 inside object o
$(root.o).bind("myEvent", function() { alert("here"); });
// 3. get the *internal* index jQuery assigned this object:
// there's only 1 property, so we just enumerate and fetch it.
var internalIndex;
for(var prop in root.o) {
internalIndex = root.o[prop];
}
// 4. delete the object
delete root.o;
// 5. query jQuery internal cache with the internal index from step 3
console.log(jQuery.cache[internalIndex].events);
​
Step 5 should log an array of all event types that were associated with the ex-o object, including "myEvent", and it's associated handler, so no the bound events handlers will not delete automatically. Here's what I see get logged (stripped out irrelevant properties):
▾ Object
▾ myEvent: Array (1)
▾ 0: Object
▸ handler: function () { alert("here"); }
namespace: ""
type: "myEvent"
length: 1
The object deletion, however, is not affected, and that will be deleted as expected. However, it is a hole in the wall kind of a situation since there is associated data somewhere in jQuery's cache that will remain there.
It seems that although you can bind events to plain JavaScript objects, you cannot unbind them. It appears jQuery assumes the object is a DOM node when unbinding, and throws the following error:
Uncaught TypeError: Object #<an Object> has no method 'removeEventListener'
Even the part about being able to bind events to objects is, I believe, undocumented. So you have to be a little careful on this, as the following will not work when trying to clean up the event handler references for that object:
$(object).remove()
$(object).unbind(..)
As a workaround, when cleaning up the Trip object, you can explicitly call removeData to do the job.
$(object).removeData();
As I've already mentioned, it's getting knee-deep with jQuery's internals, so you might want to look at an alternative solution, or be wary that library upgrades can easily break your code, which is not very unlikely.
As far as I know, you can only bind event handlers to nodes, or, in special cases, the window, document, etc. For DOM nodes, the event handlers will be removed. Even if they weren't, they wouldn't be able to be triggered anyway. Deleting the object will remove the event handlers associated with it. The event handlers should not prevent the object from being garbage collected.
Would the same occur for a dom node if
it was removed and had event handlers
bound to it?
this.handlerClick = function () { ... };
$(this.testDomNode).bind('click', this.handlerClick);
this.testDomNode.parentNode.removeChild(this.testDomNode);
Using the above code and testing with FireQuery in FireFox removing the dom node does not unbind the handler from the event,
it seems you have to explicitly unbind the handler before removing the dom node as follows:
$(this.testDomNode).unbind('click', this.handlerClick);
this.testDomNode.parentNode.removeChild(this.testDomNode);

Categories