Ok. So in my previous posts I talked a lot about various ways to detect javascript leaks, what often causes them, etc. I have to libraries that are leaking, both of which use Objects that are instantiated and reference, in one form or another, elements in the DOM.

I’ve fixed my memory leaks in my popup handler (which I’ll post about in a sec) but not yet in my toolbar script (which manages to leak in Firefox if you can believe it). My popup handler leaked for two reasons: circular references and closures.

Here are the 3 rules I have to offer to avoid these leaks:

1) NEVER store elements in the ‘this’ of an object.’
Variables that reference a DOM element - example:

var myDiv = document.getElementById('myDiv');

store that element in memory. In IE, the DOM has its own garbage collection that is seperate from the javascript collector. If the DOM garbage collector tries to clean up its memory when the page unloads and finds an element that is referenced by the Javascript, it leaves it. The javascript garbage collector… does the same thing for DOM elements referenced in javascript. Thanks Microsoft!

Normally this isn’t a problem because these references are one way. I.e. the DOM element references some javascript (myDiv.onclick = someFunction…) or vice versa. But when you have your javascript referencing the DOM element also, you create a circular reference. Now neither garbage collector can clean up the DOM element or the function/variable that references it because they each reference each other.
Variables that reference a DOM element and stored in the ‘this’ of an object
like so:

var myClass = Class.create();

myClass.prototype = {
  initialize: function(elementId) {
    this.element = $(elementId);
  }
}

Create a reference to that element in memory. This makes it exceptionally easy to end up with a circular reference in numerous ways (event handling being the most prominent). So, don’t do it. Store the id of the element and reference it in your object as $(id) over and over again. The fact that the Prototype $() function can take both an element and an id makes this pretty easy to implement.
2) NEVER store elements in variables defined outside a function (i.e. in the window) and

3) NEVER construct a function or an Object when an element is stored in a variable in scope. When a function is constructed, it has access to every element in the current scope. This means that if you define a variable that references a DOM element and then construct a function (in the same scope) that function now contains those same references. This is called closure (or lack there off) and the result is pretty much the same as circular references. Neither your object/function nor the DOM elements will be collected.

Example:

var myDiv = document.getElementById('myDiv');

function sayHi() {
  alert('hi');
}

the function sayHi(); now has a reference to $(’myDiv’); They don’t look related, but they are. This is why you shouldn’t create these references in the global (i.e. the window) scope.

Here’s another pitfall:

function sayHi(observeId, message) {
  var el = $(observeId);
  Event.observe(el, "click", function() {
    alert(message);
  });
}

The problem is that you have an element on the stack (el) and you define a function (Event.observe(….function(){…});

That function is now tied to your DOM element and neither will be collected.

So, to sum up:

NEVER store elements in the ‘this’ of an object.’
NEVER store elements in variables defined outside a function (i.e. in the window) and
NEVER construct a function or an Object when an element is stored in a variable in scope.