Julian is one of our SilverStripe developers who works with the rest of the team on large projects, as well as acting as technical lead on projects of his own. He is an American citizen who has never lived in America. He was born and grew up in Germany, studied in the UK, and is now happily living in New Zealand, whose friendly, laid-back culture and natural beauty he values.
I love using SilverStripe and Javascript to create awesome web software. I would typically create a SilverStripe CMS interface for configuring the application, then pull all the required information out of the database into a template using SilverStripe’s template language, and finally use Javascript to transform the template into a wonderfully interactive page.
However, performance can be a problem, especially in older browsers (IE6, IE7, I’m looking at you). In one major project I was involved in we discovered that pages would “hang” for 10-20 seconds in IE6, while the Javascript struggled to run. We scrambled to optimise the Javascript and managed to improve the page load time by a factor of ten. That taught me to pay special attention to Javascript performance.
This article talks about some of the optimisation techniques I have learnt. Techniques that will hopefully give you significant performance improvements for your own websites.
Golden rule: Javascript is fast, DOM is slow
Javascript code generally runs pretty quickly. The thing that causes performance trouble is whenever you hit the Document Object Model (DOM) of the page. Beware any time you query a jQuery selector, append something into the page, or bind a new listener. The monster of poor performance is lurking in the browser waiting to bite you!
jsperf.com - Javascript performance playground
Your secret weapon to help you battle the monster is this fantastic website: http://jsperf.com
You can use it to construct your own Javascript performance tests and try them in all the browsers you need to support. You can also browse the many tests other people have previously constructed. All the optimisation techniques in this article are backed up with reference to jsperf tests. So, you don’t need to take my word for it. You can see for yourself.
1. Optimise for the slowest browser
http://jsperf.com/length-detection
Take a look at the above jsperf test.
The “without tag” code runs amazingly fast in Firefox (27,970 operations per second). Does that mean you should take advantage of that and write all your selectors without a tag? No.
Take a look at the data: IE6 runs 9 (nine!) operations per second running the “without tag” code and improves to 554 operations per second when running “with tag”. So, if you have to support IE6, help it along by using using tags and over-specify the jQuery selectors.
The reason iE6 is so slow is that it doesn’t have a built-in getElementByClass function. So, if you use a jQuery selector without a tag the browser has to scan through every single element on the page looking for a match.
2. jQuery Entwine is different
http://jsperf.com/jquery-entwine-selector-test
We like to write Javascript with Entwine. The Entwine jQuery plug-in makes it easy to attach methods to DOM elements. Check it out: https://github.com/hafriedlander/jquery.entwine/
This jsperf test shows that entwine behaves differently in regards to its selectors (it uses a “delegate” binding in the background). So, if you are using Entwine, keep your selectors short and don’t specify the tags.
3. Use plan Javascript when you can
http://jsperf.com/el-attr-id-vs-el-id/2
jQuery is nice, but why use it when you don’t need to? Sometimes, when you just want the “id” of an element, you can get it using plain Javascript. No need to wrap the element with jQuery. That just introduces a performance penalty for no benefit.
4. Avoid pseudo class selectors
http://jsperf.com/id-vs-class-vs-tag-selectors/2
Pseudo class selectors are very convenient and helpful. However, they are also really slow. Refactor your code to use normal classes instead of relying on pseudo-class selectors and it will run a lot quicker.
5. Chain selectors
http://jsperf.com/ns-jq-cached
I often see code that repeats the same selector multiple times. That is such wasted effort. The browser has to query the DOM three times for the same result. Instead, save the element in a variable. Or, better yet, take advantage of jQuery’s ability to chain method calls.
6. Avoid children() unless you only want children
http://jsperf.com/jquery-selectors-context/2
This is really surprising. If you want to query sub-elements of an element, the “find” method is faster than the “children” method. You might think that because children is more specific, that would be faster. After all, it has less elements to search through. However, that isn’t the case. “Children” first needs to figure out which elements are the direct children of the parent element and that involves a depth-first search of all elements. So, effectively, “children” has to run a “find” first, then filter the results to give you only the direct children. Take home message: always use “find”, unless you specifically want “children”.
7. Use jQuery 1.7 event delegation instead of event binding
http://jsperf.com/jquery-entwine-selector-test
jQuery 1.7 has a fantastic new event handler called “on”. If you don’t know about it yet, read about it here: http://api.jquery.com/on/
Traditionally you might have bound an event to multiple DOM elements, but with “on” jQuery encourages you to use event delegation instead. The delegation approach means you only bind to a single element and intercept events as they bubble up the DOM tree. This saves a lot of time and makes the code run much faster.
8. Use window.load when appropriate
$(document).ready is a great way to run Javascript code once the DOM has loaded. However, sometimes there is some non-critical Javascript that can wait a while longer before running. For those cases, remember the handy $(window).load function. That runs once all elements on the page have fully loaded (including images, frames and objects). If you put some of your Javascript into the “window” function, that reduces the amount of code in the $(document).ready function, giving the appearance of faster Javascript.
9. SilverStripe Requirements combine and minify
SilverStripe has a nice feature (Requirements::combine_files) that automatically combines and minifies all Javascript code on a page. In “dev” mode the Javascript loads separately so you can read it all when debugging. But as soon as you switch to “live” mode the framework takes all the Javascript files, combines them into a single file, minifies them and includes the single file in the page.
Downloading, parsing and executing a single Javascript file is significantly faster than processing many separate ones. The code isn’t actually any faster, but it ends up running quicker. Using “Requirements::combine_files” is really easy, so there is no reason not to take advantage.