25 Oct 2009

Reducing memory usage with JavaScript

In this article I'll talk about reducing memory leaks in JavaScript with focus on working with jQuery and the encapsulation I used in my work ((covered in http://www.mabs.dk/wordpress/index.php/2009/04/20/reintroducing-a-little-sanity-in-working-with-javascript/ )).

This dates back to a problem I had in my previous job, our web-application used a lot of memory and it just kept growing the longer the application was running. Reloading the page only fixed the problem some of the time, often restarting the browser was the only way to free the memory.

This problem had to be fixed and fixed quickly. I found a very useful document from Microsoft about memory leak patterns in JavaScript (( http://msdn.microsoft.com/en-us/library/bb250448.aspx )),

which helped a lot.

I'll list the patterns I intend to cover by name for reference.

  1. Circular References
  2. Closures

Circular References

With circular references an object have a reference to another object, which in turn have a reference back to the other object and since the references are never removed the garbage collector will not know to free the memory.

I ran into this because I wanted to save time by not looking for DOM elements, I already had found once. Searching the DOM can be slow, especially than you have a large page with many elements.

But since it caused a lot of memory leaks, I had to find another way to do it OR live with the waste of time searching for elements. I found another way with jQuery, the function "data" (( jQuery doc: http://docs.jquery.com/Core/data )) solves the problem in a nice and clean way.

I'll show a simple example, how this can be used. But first I'll show a piece of code which a potential memory leak.

	// Find the elements in the DOM-tree
	var el1 = $( "#element1" );
	var el2 = $( "#element2" );

	// Creating circular reference problem
	el1[0].other_element = el2;
	el2[0].other_element = el1;
…
	// Accessing element-reference
	el1[0].other_element.css( "background-color", "black" );
	el2[0].other_element.css( "background-color", "#666000" );
…
	// Removing the reference
	el1[0].other_element = undefined;
	el2[0].other_element = undefined;

And here is a piece of code which don't have the potential memory leak:

	// Find the elements in the DOM-tree
	var el1 = $( "#element1" );
	var el2 = $( "#element2" );

	// Add the reference with the data-function
	el1.data( "other_element", el2 );
	el2.data( "other_element", el1 );
…
	// Accessing the element with the data-function
	el1.data( "other_element" ).css( "background-color", "black" );
	el2.data( "other_element" ).css( "background-color", "#666000" );
…
	// Removing the reference
	el1.removeData( "other_element" );
	el2.removeData( "other_element" );

As you can see there isn't much difference in the two code examples, but there is a huge difference between in potential for memory leaks.

This works because jQuery will remove the data when the DOM-element is removed (through jQuery). You should note that if you remove el1 and not el2, el1 will still be referenced from el2 until el2 is removed.

The problem and solution for closures.

Now with jQuery, we tend to create a lot of new functions when we add events, loops through arrays and so on. We need to be careful then we do this, since it can lead to the problem described in that Microsoft document as "Closure".
Basically the problem is that inside function (A) we create a new function (B),

if B references to an outside object we have an extra reference and reference count for the element never reaches zero, this means that the garbage collector doesn't free the memory.

Like before I'll give an example with the potential problem followed by an example without the problem.

function A( el ) {
	var el2 = $( "<div>Hallo</div>" ).appendTo( document.body );
	el2.css( {
		"position" : "absolute",
		"top" : 100,
		"left" : 100,
		"background-color" : "#666000"
	} ).hide( );

	el.click( function( e ) {
		el.hide( );
		el2.show( );
	} );

	el2.click( function( e ) {
		el.show( );
		el2.hide( );
	} );
}

Without the potential problem:

function B( e ) {
	var el = e.data.el;
	var el2 = e.data.el2;
	el.hide( );
	el2.show( );
}

function C( e ) {
	var el = e.data.el;
	var el2 = e.data.el2;
	el.show( );
	el2.hide( );
}

function A( el ) {
	var el2 = $( "<div>Hallo</div>" ).appendTo( document.body );
	el2.css( {
		"position" : "absolute",
		"top" : 100,
		"left" : 100,
		"background-color" : "#666000"
	} ).hide( );

	el.bind( "click",  {
		el : el,
		el2 : el2
	}, B );

	el.bind( "click",  {
		el : el,
		el2 : el2
	}, B );
}

In the first example I have two anonymous child functions in function A, every single time A is called two new enclosures are created from these two functions. Each of the child functions have references to elements from function A's scope potential preventing the garbage collector from deleting the elements.

In the second example I've moved the two functions out of function A and instead just give a reference to the functions inside function A when I bind the events, but in order for the functions B and C to access to the elements I use the jquery-function "bind" insteed of "click" to bind the events, "bind" allows me to provide a data-object to the function. In the event-function I can easily access the data by accessing the parameter "data" on the event object.

The reason this works is that the two closures are no longer created and the references to the element are parsed by normal reference through the event-object. Jquery will unbind all events from an element once it's removed, eliminating the reference created by parsing the data-object to bind.

Another way you can achieve this is with the "data"-function like this:

function B( e ) {
	var el = $( this );
	var el2 = el.data( "el2" );
	el.hide( );
	el2.show( );
}

function C( e ) {
	var el2 = $( this );
	var el = el.data( "el" );
	el.show( );
	el2.hide( );
}

function A( el ) {
	var el2 = $( "<div>Hallo</div>" ).appendTo( document.body );
	el2.css( {
		"position" : "absolute",
		"top" : 100,
		"left" : 100,
		"background-color" : "#666000"
	} ).hide( );

	el.data( "el2", el2 );
	el2.data( "el", el );

	el.click( B );
	el2.click( B );
}

The major difference here is that the data is no longer private. Every piece of code that can get a hold of either of the elements can get to the other through the data-function.

But if the data is not meant to be private this can be fine. I've done this in a few jQuery plugins I've created where is was better to just add the element references as data so that all my functions for the plugin could easily reach the elements that made up the interface.

Saving memory on events

Another way the save memory is to use the same event-function for more then one element, especially if the functionality is similar. Here I'll show how I tend to use a single event for two buttons.

…
	<button xaction='okay'>Okay</button>
	<button xaction='cancel'>Cancel</button>
…

I add the attribution "xaction" to the elements to indicate what kind of action I want to take then the button is clicked.

Here are the JavaScript-code the handle the click event on these two buttons.

function button_clicked( e ) {
	var el = $( e.target );
	var xaction = el.attr( "xaction" );
	if ( xaction == "okay" ) {
		// TODO: Handle okay action.
	} else if ( xaction == "cancel" ) {
		// TODO: Handle cancel action.
	}
}
…
$( "button[xaction]" ).click( button_clicked );
...

It's not that a function uses a lot of memory, but if you have a lot of functions that could become fewer by refactoring the code, it can add up to a lot of memory.

Another reason I like this is the refactoring of code it self. I don't like to have functions that have 95% of the same functionality, then we can create a function that handles the 95% plus the last 5% percent from multiple functions.


Tags:


Click to share