Skip to content

Instantly share code, notes, and snippets.

@jupiterjs
Created July 5, 2011 04:50
Show Gist options
  • Save jupiterjs/1064263 to your computer and use it in GitHub Desktop.
Save jupiterjs/1064263 to your computer and use it in GitHub Desktop.
Templated Event Binding

Templated Event Binding

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.

Automatic Cleaning

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.

A Leaking Tooltip

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.

Unbind with jQuery

jQuery helps you unbind event handlers in a number of ways:

Remove the bound element

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!

Unbind directly

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

Use namespaces

How to know when to unbind?

Controller

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();
  }
})

How this fits with MVC

Listening to Model events

Listening to external things

Video player

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment