Clean up after your event listeners

Memory leaks in Javascript are, unfortunately, a very real problem. Of course, it seems to be a problem mostly in IE (try disabling all add-ons if you think you're seeing them in Firefox). While there are a number of ways that memory can be leaked, I find that one of the most significant (and most frequent) causes is failing to remove event handlers.

Important Update: I failed to mention when I originally wrote this post, that you should use caution with this 'unload' technique. If you are manually unhooking events throughout the life of your web-app (or taking care to manually unhook them on 'unload'), then you might potentially be adding multiple (and unnecessary) 'unload' handlers to the browser.

This might not seem like a big deal, but in a large and complex app it can actually increase memory consumption dramatically, thereby negating any gain you'd have gotten from the technique!

Auto Detach with 'unload'

In IE, if you wire up an event listener and fail to remove that listener when your page unloads, you're probably asking for a leak. One effective technique to ensure that listeners get removed is to wire up an 'unload' event to the window which detaches the listener for you. For example:

var elem = document.getElementById('someElem');
elem.attachEvent('onclick', elemClick);
window.attachEvent('onunload', function(e)
{
   elem.detachEvent('onclick', elemClick);
});

Keen readers might have noticed that I solved one problem (removing the 'click' listener from elem) by creating another one (attaching a new, anonymous listener to the windows's 'unload' event).

Why is this a problem?

In a previous blog entry, I mentioned that it wasn't possible to remove an event listener if that listener were anonymous, as in:

// IE specific code
var elem = document.getElementById('someElem');
elem.attachEvent('onclick', function(e)
{
   alert('you clicked it!');
});

elem.detachEvent('onclick', function(e)      // WON'T WORK!
{
   alert('you clicked it!');
});

While it's true that the above code won't work, it's not entirely true to say that it's impossible to remove an anonymous listener. Why? Because every function actually has a reference to itself through its own arguments object. Specifically, through the .callee property of the arguments object.

We can use this to our advantage to detach the anonymous 'unload' listener from within the listener itself.

var elem = document.getElementById('someElem');
elem.attachEvent('onclick', elemClick);
window.attachEvent('onunload', function(e)
{
   elem.detachEvent('onclick', elemClick);
   window.detachEvent('onunload', arguments.callee);     // detach this anonymous listener
});

Why not just un-anonymize?

You certainly could just name all of your functions and then refer to them by name, but sometimes anonymous functions are very useful since they allow to create closures and do other nifty Javascript tricks. In those cases where it's desirable to use an anonymous function, this technique might be just the ticket!

Conclusion

I've conveniently (for me anyway) left out any non IE-specific code in this post. It's worth nothing that I don't generally use this technique except in IE, which means a little cross browser code is in order to ensure that this 'onunload' technique is only used for that browser.

Of course, it isn't going to hurt anything for other browsers (and it might even help) so there'd be no harm is using it across the board, bu you'll still need to write some cross-browser code (obviously). I'll leave that as an exercise for the reader though.

Happy scripting.

Comments welcome.

One Response

  1. A Says:

    It doesn't work when you try to detach an anonymous function using arguments.callee in IE7

    to do so in IE7 you have to save the anonymous function into another global var
    and then later detach it using that var

Got something to say?

Please note: Constructive criticism is welcome. Rude or vulgar comments however, are not and will be removed during moderation.