Intro
Here at Sunview we create a lot of web applications for people. We are always looking to give the end user a great experience. This means that the user interface needs to be snappy and responsive.
jQuery is used extensively to create user interfaces for our clients. Also creating custom user interfaces happens quite a bit, since every client needs something unique. This means we are writing lots of code that does some heavy DOM manipulation. Making sure that we do this intelligently is important.
Right to the Test
If you want to skip all the boring explanation and jump right into running the test and viewing the code, go right ahead. You can by going to http://sunviewconsulting.ca/blog/2011_05_25/index.html. Be careful, since this is essentially a load test, this may cause your browser to become un-repsonsive.
Caching elements with jQuery
jQuery is all about making DOM access and manipulation easy. We can select something by id.
<div id='selectme'>Some HTML</div>
$('#selectme').html('New HTML');
Or we can select something by CSS class
<div class='selectme'>Some HTML</div>
$('div.selectme').html('New HTML');
If you are a web dev, then you should already know that selecting by ID with jQuery will always be much faster than selecting by class. The reason is that selecting by ID will use the browsers native getElementByID method. This is great, except we won’t always be able to select things by ID for a few reasons.
- Each ID must be unique within an HTML document
- We would need to know the ID before hand (for dynamically generated stuff this may not be possible).
- Maybe we want to select more than one element at a time.
So if we are selecting a HTML DOM element using jQuery, it could be an expensive operation. We want to minimize this cost. Lets look at this jQuery/JavaScript snippet.
for (var i=0; i<10; i++)
{
$('ul.menu').append('<li>' + i + '</li>');
}
Straight forward loop. It finds the <ul> element with a class of menu, and appends a list item to it. This works great.
However, we are using jQuery to lookup the <ul> element on each iteration. This could be a hugely expensive operation if we have thousands of elements within the DOM. Don’t get me wrong, jQuery and it’s Sizzle engine is optimized and frankly bad-ass, but at the end of the day it’s still doing some heavy lifting. Help it out by caching that <ul> element rather than looking it up each time.
var ul = $('ul.menu');
for (var i=0; i<10; i++)
{
ul.append('<li>' + i + '</li>');
}
Ahhh, much better. Now we find the <ul> element once, store it in a local variable, and then go through the loop. Instead of 10 queries, jQuery has done 1. We’ve got the same results but a happier web browser and a happier end user.
Big String or Lots of Element Insertion?
So there is potentially another optimization we could do to that above loop. We are adding an element to the HTML DOM on each iteration. Now since JavaScript isn’t threaded, we shouldn’t be causing any UI updates to occur, but we may be causing a re-org of the browsers internal HTML DOM tree on each insert. We could instead generate a large HTML fragment string and insert all at once at the end of the loop.
var html = '';
for (var i=0; i<10; i++)
{
html += '<li>' + i + '</li>';
}
$('ul.menu').append(html);
Now I’m not sure if this will be faster than the above method, because jQuery will be using different internal implementation behind the scenes depending on what browser you are using. So we will result to good ol’ un-scientific test to determine the best course of action.
The Tests
The test page contains five different tests using combinations of the above methods. Each test has the same effect on the DOM. We are going to dynamically generate 100 <ul> elements and within each of those <ul> elements we are going to append 100 <li> elements. Maybe not something you would normally do in real life, but fits the purpose of this blog post well enough.
By Class – No Cache
This is the most naive approach. Access each <ul> tag by it’s class name, and don’t even bother caching it while adding it’s <li> children.
By Class – Cache
Must better! Access each <ul> tag by it’s class name, but this time, cache it for the inner loop to add children.
By ID – No Cache
We know this will be fast, but maybe you won’t always be able to do this? Either way, access each <ul> tag by ID and don’t bother caching it while adding it’s <li> children.
By ID – Cache
Best of both worlds. Access that <ul> by ID and cache it while adding children.
By String
The potentially whacky method of generating a large HTML fragment and adding it all at once to the DOM. Doesn’t really matter if we access the <ul> by ID or by class to demonstrate this.
The Testing Rig
To different browsers we are using a MacBook Pro
- MAC OS X 10.6.4
- 2.8 GHz Intel Core 2 Duo
- 8 GB 1066 DDR3
And while we are at it we are also testing some mobile devices
- iPhone 4 (iOS 4.2)
- BlackBerry Bold 9780 v6.0
- iOS Simulator (iOS 4.2)
The browsers we are testing are
- Chrome 11
- Firefox 5.0
- Safari 5.0.1
- IE 9.0
- IE 8.0
- IE 7.0
- IE 6.0
The Results
All browsers. 10,000 elements added. Vertical axis is time (ms).
IE removed (for being too slow). 10,000 elements added. Vertical axis is time (ms).
Mobile browsers. 625 elements added. Vertical axis is time (ms).
The Conclusion.
As we expected, querying for elements by ID is the fastest across the board. However, using some intelligent caching you can use class selectors and get similar performance.
Using the large HTML string fragment also worked very well, almost winning across the board. The only exception is FireFox 5. If anyone knows the technical reasons why FireFox 5′s internal implementation of this is slower I would love to know why.
The mobile browsers also did very well (for a much smaller dataset of course). We see the BlackBerry browser holding well against the iPhone in terms of performance. (Which is not surprising since they are both webkit based).




Great article. These are obvious optimization tricks that I should put in practice more often. Especially since Javascript can be so demanding on older hardware/browsers.
Thanks! Yes, for Chrome or Firefox everything is pretty fast. But Mobile or IE6 a lot of apps can perform sluggish.
I would like to see some more numbers for mobile browsers. It’s getting to the point where all new desktop browsers are pretty fast but I feel there would be a lot of fragmentation on mobile.
You are correct. I will be doing much more mobile focused stuff so check back in a few weeks!
Why don’t you do some real software engineering, forgot about web toys.
Don’t feed the trolls.
Pingback: 網站製作學習誌 » [Web] 連結分享
hum, i guess firefox implementation of the += is slow, maybe a test using
var html = [];
for (var i=0; i<10; i++)
{
html.push('' + i + '');
}
$('ul.menu').append(html.join(''));
would have been faster
Thanks for the post! Yes, you are absolutely right. When people think DRY then tend to just think about literally repeating code. DRY also applies to the effect that code has. You are correct in that this isn’t really caching. But it was the sexiest description I could think of.