Performance Blog: How to Get the Most Out of JavaScript Part II
In the first part of this topic, we looked at how JavaScript impacts page rendering and how to structure HTML documents that include JavaScript so as to reduce the well documented effects of "render blocking" by scripts. By improving the rendering speed, we can improve the overall performance and, most importantly, user experience. Its possible to knock several seconds off the load time just by tweaking where and how your scripts are invoked.
In this follow up, we will look at some general tips for writing better / faster JavaScript code.
In the previous article we established one big fat rule that can improve overall performance: only include whatever JavaScript is needed for the task at hand, and to include that in the least intrusive way. That might seem obvious, but web application frameworks often gratuitously throw a lot of JavaScript into the mix in the name of making it easier for the developer to do his/her thing. Sometimes not with performance in mind at all. And sometimes we incorporate code meant for very specific situations and that code winds up in the global context. And then as projects mature and we "improve" things, old, unused code gets left in the mix. So be circumspect. Use Firebug or webkit developer tools to see what is happening on each page load. Look at the waterfall charts on the Network tab, and then at where slow downs occur and what causes render blocking. Is all that stuff really necessary? Could it all be done with less code? Better code? Could the document by structured better to mitigate render blocking?
With that said, let's look at JavaScript libraries, like the popular jQuery library. These libraries do a number of very useful things:
- Solve cross-browser compatibility issues
- Add language features that aren't natively available
- Make writing JavaScript "easier", and thus more accessible to the less technically inclined
- Extensible via plugins
Its hard to imagine us writing any significant web code these days without using jQuery (or comparable). It solves so many problems for developers, not the least of which is getting the finished product out the door quickly, with the least amount of pain.
But there is some cost to this .... libraries like jQuery are supersets of JavaScript and as such add layers of additional code to do what they do. Its a matter of physics at some point: more lines of code => more execution time. There is a definite trade-off: ease of use for a (small) sacrifice in performance. And to be fair, the trade-off in many real world use cases is so small as to be negligible with the benefits outweighing the costs.
To illustrate some notable differences, a crude benchmark I did on jsperf.com showed the native JavaScript document.getElementById("test").innerHTML to be approximately 14 times faster than the comparable jQuery expression of $("#test").html() . On this test, the native document.getElementById("test") is a striking 125 times faster than a common jQuery variant $("div#test"), and 164 times faster than $("div div#test")! Whoa! You are reading those numbers correctly! The end result returned by each of these expressions is identical. These are just variations of how to get to the same end result.
We are talking microseconds so let's not throw the baby out with the bath water, at least not just yet! Where we really need speed, native is the way to go. So where we might want to consider bypassing the overall usefulness of jQuery and friends for pure JavaScript are use cases where the performance trade-off might have a measurable impact on performance and user experience:
- Where we are manipulating large amounts of HTML
- While executing code in loops with many iterations
- Where JavaScript is the primary development tool for an application of some significance
- For slower devices, like mobile browsers
Loops especially should be looked at critically to make sure they are as efficient as possible.
There are also syntax variations themselves which can be slow. The following is redundant and causes unnecessary parsing: $("div div p#my_id"). Perhaps this may look more specific, but its not. The id all by itself is unique, and needs no additional qualifiers. The additional qualifiers require more parsing.
This code will execute faster: $("#my_id"). No need for anything else, the id says it all. Anytime you can use an id, you are going to be better off. And if there is an id in your selector chain, it should be the left most object. So $("article div#my_div a"), is inefficient and better expressed as $("#my_div a"). Its faster.
The following table demonstrates a number of different syntax variations to access the exact same DOM element, and how efficient they are relative to the benchmark of the native JavaScript method, getElementById(). The tests were run using Chrome with jQuery 1.10.2 and a small sample block of HTML taken from a Drupal site. There is a nested div with both an id and className of "test". The test itself is available at http://jsperf.com/testhal/2.
Selector | Speed |
---|---|
getElementById( ‘test’ ) |
1x |
$( ‘#test’ ) |
14x slower |
$(‘.test’) |
22x slower |
$( ‘div#test’ ) |
125x slower |
$(‘div #test’) |
20x slower |
$(‘div div#test’) |
164x slower |
$( ‘div.test’ ) |
151x slower |
HTML documents should also be constructed such that those elements which will be acted upon are readily accessible with as few selectors as possible, and with proper consideration for using id's. The more selectors used to reference a given element, equates into more overhead.
Avoid manipulating styles directly with JavaScript. Add or remove classes to manipulate styles such that the browser only has to do one re-flow. Bad: $("#my_div li").css("font-weight", "bold"). Good: $("#my_div li").addClass( "bold-text"). This has potential performance benefits (especially where there are multiple elements at play), and is best practices where there are both designers and developers working on the same project.
In fact, any time CSS can be used to manipulate something that is preferred over JavaScript. Its faster in most cases. This is especially important in mobile development where mobile devices are significantly slower at executing JavaScript.
Other tips:
- Avoid using "eval", "with", and "for-in" loops
- Use locally scoped variables, always declared with "var"
- Cache references to repeatedly used objects by saving them as variables
We've looked at some basic tips on how to write better performing JavaScript in this article. There are number of other advanced considerations such as proper use of closures, variable scoping, objects vs arrays, avoiding memory leaks, CPU profiling and others that we'll save for another day.
Resources
- jsfiddle.net, a great place to test and share code
- jsperf.com, a great place for testing js performance and sharing code
- jshint.com, great place to make sure your code is clean and lint free
- Anything by Steve Souders
- Great site for testing web page performance: webpagetest.org