Avoid the Zombie Apocalypse

Memory leaks are like zombies. Just a couple and you can easily navigate around their outstretched arms and groans. But when you have a pack of zombies, you’ve got a real problem. CanJS will help you avoid the zombie apocalypse.

posted in open-source, canjs, Development on April 10, 2012 by Brian Moschel

Memory leaks are an extremely common problem in JavaScript applications.

Memory leaks are like zombies. Just a couple and you can easily navigate around their outstretched arms and groans. But when you have a pack of zombies, you’ve got a real problem.

The two most common sources of leaks are event handlers and unused data objects. CanJS handles these leaks for developers automatically. This article will explain those leaks and how CanJS solves them. This article is an expanded explanation that was started in the Introducing CanJS post.

CanJS will help you avoid the zombie apocalypse.

Event Handler Leaks

Say you’re creating a tooltip widget that shows when you mouseover an element. You might use it like:

$("h1").bind("mouseenter",function(){
  $("<div>Undo</div>").tooltipFor(this)
})

Here’s an example of a leaking tooltip widget. Can you spot the leak?

$.fn.tooltipFor = function(anchor){
  var $el = this
      .appendTo(document.body)
      .offset( $(anchor).offset() )
  
  $(anchor).bind("mouseleave",function(ev){
      $el.remove()
  })
}

On mouseleave, the event handler doesn’t remove itself. Even worse, it has a closure that references the tooltip’s element. The browser’s garbage collector can’t clean up the event handler or the element. Imagine how this problem would grow over time:

CanJS solves this with templated event handlers. Here’s that same tooltip with can.Control:

var Tooltip = can.Control({
  init: function( el, options ) {
    el.appendTo(document.body)
      .offset( $(options.anchor).offset() )    
  },
  "{anchor} mouseleave": function( el, ev ) {
    this.element.remove();
  }
});

new Tooltip($("<div>Undo</div>"),{
  anchor : this
});

can.Control keeps a reference to all event handlers created for each control, listens for the control’s parent element to be removed, and unbinds all event handlers. Controls made with can.Control don’t leak.

Data Object Leaks

Another common source of leaks are long lived data objects that are never cleaned up.

Apps often need multiple collections of data that may contain the same instance. For example, imagine a todo app that is displaying two lists of todos: critical todos and today’s todos. Both lists contain the same instance (write talk on CanJS).

When this item is checked as complete, both lists need to reflect the change. Many libraries provide a global store for data objects to deal with this problem. Both lists would reference the same todo instance, so changes in one place are reflected in the other. For long-lived applications, you are constantly collecting unused instances, which fills memory. The developer has to tell the store when to flush instances.

CanJS solves this by only keeping data objects around when a control (or a view) has bound an event handler to that object’s property changes. For example, if this were using EJS, the following template snippet would live bind to the todo model instance:

<li class=“<%= todo.attr(‘complete’) ? “done” :”” %>” >

Everything in the global store is a can.Observe object, so whenever an object has a listener on any property, it is kept around. When that listener is removed (maybe a control has removed the element displaying its data), can.Model removes the instance from the store.

When these two todo lists are removed from the page, all event handlers are removed, causing can.Control to unbind all event handlers, and EJS unbinds event handlers listening on all model instance property changes. All data is cleaned up from the page, automatically and with zero effort.

This is a critical problem for client-side MVC. It is almost impossible to create a leaking app with CanJS.

comments powered by Disqus
Contact Us
(312) 620-0386 | contact@bitovi.com
 or cancel