Forgetting to unbind event handlers is the easiest way to create memory leaks. This is an extremely common problem that typically requires a lot of boilerplate unbinding to solve. JavaScriptMVC 3.1's $.Controller now supports templated event binding, making it event easier to create widgets that clean themselves automatically.
Before we discuss the problem of unbinding event handlers, how jQuery addresses it, and how $.Controller makes it almost impossible to write leaking code, it's worth reviewing part of what makes a perfect plugin (widget).
A perfect widget is many things, extendable, deterministic, etc. But critically for this article, no matter how you use it, it is memory leak free.
To remove a widget and all of its resources, I should simply be able to remove the element from the page like:
$('#tooltip').remove();
But as we will see, if the widget is listening to events outside it's element and children, it's easy to create leaks.
To understand the problem of event handler leaking, consider a simple tooltip widget. The tooltip works by
$('#tooltip').tooltip("Here is the text")
The code for this tooltip might look like:
$.fn.tooltip = function(html){
var el = $('<p>').html(html),
offset = this.offset();
el.appendTo(document.body)
el.offset({
top: offset.top + this.height(),
left: offset.left + this.width()
})
$(document.body).click(function(){
el.remove();
})
}
Do you see what's wrong? This code does not error, instead it leaks. If you can't spot the leak, don't feel bad, we've seen this mistake many, many times.
The problem is that although the element is removed, the body's click handler is not unbound. This function is still referenced by the DOM. And worse, this function has the paragraph element in it's closure. Thus, the paragraph element and any children it might have will be kept in memory until the page refreshes.
jQuery helps you unbind event handlers in a number of ways:
If you remove an element from the DOM, all of its event handlers will be cleaned up. For example:
$('#foo').click(function(){
// event handler code
});
// sometime later
$('#foo').remove()
However, this only works if you are using jQuery's DOM modifiers to remove the element like:
$('#foo').parent().html("");
$('#foo').parent().empty();
$('#foo').replaceWith("<p>Some Content</p>");
This is why you should rarely use direct DOM modifiers like:
$('#foo')[0].parentNode.removeChild( $('#foo')[0] );
If you do this, your event handler will sit around in memory forever!
If you bind an event handler, you can always just
First, if an element is removed from the DOM, jQuery unbinds all of its event handlers. This means that
If you are d
Controller has always been useful for unbinding and unbinding.
Previously used bind and delegate to listen on elements outside the controller's element.
Now you can listen directly.
$.Controller('Tooltip',{
init : function(element, message){
this.element.html(message)
},
"{document.body} click" : function(){
this.element.remove();
}
})
Video player