Optimise your JavaScript

Optimise your JavaScript

  • Knowledge needed: Intermediate JavaScript
  • Requires: Firefox with Firebug Profiler
  • Project time: 1 hour to read and understand it. Project time will vary.

Speed up your sites with these tips for slashing the time it takes to execute your code, particularly in older browsers, provided by Joe Angus from We Love

UPDATE: Below is a revision of my article, taking on board the comments and considerations from various readers over the last few days. Rather than retracting this article, I wanted a chance to rectify it with the correct information. I appreciate the time and effort gone into the responses. We live in a digital environment and it’s great that this is possible; it means that we’re always learning.

JavaScript has become essential for all dotcoms wishing to deliver a rich user experience, especially in projects requiring you to avoid Flash. As the power of consumer hardware advances, limitations are vanishing, enabling your sites to perform tasks that only a few years ago would have ground any browser to a halt. Nevertheless, optimising code is still vital for a seamless user experience, so I would like to share some simple rules that can dramatically decrease the time it takes to execute your JavaScript, specifically in older browsers.

Many of these optimisations may not be noticeable if you aren’t working with large sets of data requiring you to iterate a task many times – but even so, I would recommend always adhering to the guidelines below where practical. That way, when you do have to streamline your JavaScript, a large chunk of the work will be done already.

It’s also important to know your target audience, in terms of browser(s) and device(s). For example, in some cases, running jsPerf tests (see below) on my iPhone or iPad gets completely different results to desktop browsers.

Getting started

The very first thing you should do is profile your JavaScript to understand exactly which methods are taking a long time to execute. I would recommend using Firebug’s profiler (if you aren’t aware of what Firebug is, then make sure you go and download it, preferably for Firefox, before continuing to read this article). It’s incredibly easy to use, and you can quickly profile your code without necessarily having any additional work on your part.

There is also jsPerf, which is a fantastic way of creating and sharing test cases, although not as quick as FireBug’s profiler in terms of getting a quick snapsnot.

Firebug is invaluable for profiling your code. Download it from www.getfirebug.com
Firebug is invaluable for profiling your code. Download it from www.getfirebug.com

Accessing the DOM

It’s quite likely that your JavaScript will be accessing the DOM, and actually it’s more than likely the cause of any performance issues. It’s very important to understand that this is an expensive task, due to the fact that the browser has to re-render the screen every time the DOM has changed. I would first try removing your DOM calls to gauge just how expensive they’re being before tackling the smaller improvements below.

Where possible, it’s quicker to manipulate the DOM in one large hit, rather than many little ones, reducing the amount of times the browser has to update. Instead of appending many elements directly, try injecting them into a document fragment, and then appending the fragment into your document (read more about document fragments).

The same can be said about applying inline styles directly onto elements. Instead, add and remove classes to minimise browser reflow time.

Construct code to perform any changes to the DOM in one go, to minimise the number of times the screen is redrawn
Construct code to perform any changes to the DOM in one go, to minimise the number of times the screen is redrawn

Building strings

Older versions of Internet Explorer (versions 5-7: 8 does not display the same problem) get slower and slower the longer your string is. We'll fix this below.

Note that in newer browsers, you won’t see any improvement. In fact, in many cases you will see the opposite – all the more reason to profile your code to see if the benefits in older browsers outweigh the impact on newer alternatives, and decide which your target audience would most benefit from.

If your strings are short, stick to the first method shown below.

Fastest method in most cases:

  1. var string   = 'abc';
  2.     string += 'def';
  3.     string += 'ghi';
  4.     string += 'jkl';
  5.     string += 'mno';
  6.  
  7. alert(string);

Try doing this for old browsers struggling to concatenate large strings quickly:

  1. var string = ['abc...', 'def...', 'ghi...', 'jkl...', 'mno...'];
  2.  
  3. alert(string.join(""));

Speeding up them loops

By simply rearranging for loops, you can squeeze a little more performance out of your code.

Change from:

  1. for(var i = 0; i < my_array.length; i++){ }

Or:

  1. for(var i = 0; i < myMethod(); i++){ }

Instead, try this subtle change:

  1. for (var i = 0, len = my_array.length; i < len; i++) { }

Or:

  1. var len = myMethod();
  2. for(var i = 0; i < len; i++){ }

Raymond Julin has kindly has put up a jsPerf test case so you can see the difference yourself. In literally every browser, you can see a nice performance gain.

Ensure that there are no unnecessary function calls within your loops. If you can do it once, outside of the loop, it’s usually a good idea to do so. The performance gain will depend upon how expensive the function is.

Avoid:

  1. for(var i = 0, len = my_array.length; i < len; i++){
  2.   var response = goDoSomething();
  3. }

If it’s appropriate, try:

  1. var response = goDoSomething();
  2. for(var i = 0, len = my_array.length; i < len; i++){
  3.  
  4. }

Another optimisation to consider, when you aren’t reliant on the order of the loop, is to decrement instead:

  1. var i = my_array.length;
  2. while( i-- ) {  }

I would use this last method sparingly: it’s not the most logical way to loop and could make it more difficult for other programmers needing to understand your code, but it could still be useful in the right scenario.

Calling methods or properties on array elements

If you’re accessing properties or methods on elements within an array multiple times, it’s faster to declare a variable holding that element. It can mean you need extra lines of code, but if it's a regular occurrence, the resulting performance gain can make it worthwhile.

For example:

  1. myArray[myIndex].myMethod1();
  2. myArray[myIndex].myMethod2();
  3. myArray[myIndex].my_variable;

...is slower than:

  1. var element = myArray[myIndex];
  2. element.myMethod1();
  3. element.myMethod2();
  4. element.my_variable;

Again, Raymond has a jsPerf test case for this one where you can see the performance gains across browsers.

This is also true for any lookups, not just arrays. The more you can avoid doing multiple lookups, the better.

The scope chain determines the order of operations JavaScript performs when finding a referenced variable
The scope chain determines the order of operations JavaScript performs when finding a referenced variable

Local versus Global variables

Whenever you reference a variable, JavaScript will look through the scope chain in order to find it. First, it will look through the local variables within the current scope, and then work its way up through the chain to the globals. Therefore, accessing global variables is far slower than accessing local ones, with varying effects depending upon the level of nested scope.

For example:

  1. var my_var = 1;
  2. MyClass = function(){
  3.   var my_var = 1;
  4. }

Within the scope of an object of the above class, my_var is its local variable and the first place within the scope chain JavaScript will look at. This is quicker than if the local declaration didn’t exist, and it had to move up the chain to find the global variable.

Sometimes it’s possible that you will need access to a global value within a different scope, especially in loops, in which case it’s more efficient to bring that variable into the local scope purely to access the local version within the iterations.

  1. var global_var = 20;
  2. function MyFunc(){
  3.   var local_var = global_var;
  4.   for(var i = 0, length = 999; i <= length; i++){
  5.     // Now you can access the local version for quicker access times.
  6.   }
  7. }

It’s also worth remembering that if you don’t put ‘var’ in front of your declarations, it will automatically be created as a global variable. For that reason alone, it’s good practice always to declare the scope of variables. This can also help if you’re debugging a scope problem.

Conclusion

Some of the techniques above will give you far less noticeable performance gains than others, and there are many more techniques which I haven't covered (for example, bit shifting). However, these are the methods I’ve found most effective for getting the most performance out of my JavaScript in older browsers. Remember to look into your DOM calls first, as they’re far more expensive and can improve performance far more noticeably than the other methods.

Please do add your comments below to let me know your opinions and how you’ve managed to work around potential performance-related problems in your own JavaScript.

14 comments

Comment: 1

Is the prototype.localMethod example the wrong way around?
(in regards to which is faster?)

Using benchmark.js, MyClass.prototype.localMethod = function(){ } shows to be over 90% slower than using MyClass = function(){ this.localMethod = function(){ } on the latest chrome build?

Comment: 2

I also get the opposite result on the string building example.

are your results browser specific?

what test methods did you use to come up with your results?

Comment: 3

Hi Atmd,

With evolving and varying browser JS engines, results do vary quite a bit. Usually when I'm optimising JS it's due to older browsers (such as IE7) not being able to cope, therefore I tend to do most of my benchmarking within that area.

JS engine differences are particularly evident the string building example, the latest JS engines are optimized for string concatenation operators. Array joins remain fast, but are without a performance gain when testing in latest Chrome/FF for example. As always, it's important your JS doesn't ground to a halt in older browsers (e.g. IE7) so I still believe the best practice, and performance is to use Array joins.

Regarding the prototype localMethod example, you need to give it some context to see the real world outcome, maybe I should have mentioned that a bit better in the above article. In this particular example we're looking at the initiation time of an object, not the class creation time. I wrote a quick test using benchmark.js as you suggested and I get the following results..

FireFox (13.0.1)

Using prototype - x 2,832,867 ops/sec ±1.47% (63 runs sampled)
Using enclosed - x 904,118 ops/sec ±5.71% (52 runs sampled)

Chrome (20.0.1132.47)

Using prototype - x 96,589,867 ops/sec ±5.88% (76 runs sampled)
Using enclosed - x 386,212 ops/sec ±33.46% (48 runs sampled)

In both browsers, the prototype method is far faster. If you had just 1 single instance of your class, then it would be the other way around - but one would question why you're using OOP if you aren't ever creating more than one object ;-)

I've uploaded the page so you can see / run the test yourself. http://labs.welovedigital.com/objects.html

Let me know your outcome, any many thanks for reading the article :-)

Regards,

Joe

Comment: 4

Hi Joe,

JavaScript String object offers a concat method through which you can perform the string concatenation, which I thought worth a mention.

Regards

JP

Comment: 5

Hi JP,

The concat method from previous experience can be quite fast, but I would typically use it to join arrays together, rather than strings , although it does work just fine with the latter. When working with large strings, I find doing a simple .join("") far easier to read and explain, than a for loop concatenating each string together.

I'm pretty sure older IE's had a terrible performance hit when using concat too, which is why I tend to shy away. You're absolutely right though, concat does deserve a mention.

The string concatenation debate is quite a loose one, with every browser being different along with the actual use-case playing a huge part in which approach you decide to utilise. It's not unusual for me personally, to use multiple methods within the same project, it all depends on the individual function aim you're trying to achieve.

Thanks for reading,

Regards,

Joe

Comment: 6

hi wilco3d,

thanks for the reply, certainly cleared some valid points up

Some thing that is missing from a lot of tutorials/articles is guides for optimising JavaScript for older browser and the issue you get with older versions of IE such as recursion limits.

What are your thoughts on using AMD to use different code for different browsers?

Iv seen benchmark give drastically different marks for webkit vs mozilla browsers, and have been wondering if its worth it, on large js applications to bring in different modules, optimised for the users browser, the same way people have been doing with css for years

Comment: 7

Hi Atmd,

AMD is definitely interesting, especially for large scale web applications which use JS extensively and need to work efficiently cross browser. It's kind of 'responsive' JavaScript, and would ultimately deliver the best experience per browser... in theory anyway.

Being modular always has the benefit of simultaneous work streams, having different developers working on different modules can be a great way to divide work load without too much stress of making sure it all works together. There's also the benefit of some good code organisation.

There are a few issue's I would see though, and forgive me if this isn't entirely accurate, I've not had a chance to use AMD in the real world, so it's purely based upon what I've read.

One would be the overhead required to not just create, but also maintain and update various versions of your JS. This could be minimised by making use of JS's prototypal inheritance - so that you could only overwrite the methods you wish to enhance for a particular browser, leaving the 'core' to be the same across your project (another reason I much prefer the prototype method over encapsulated).

In short (as I think the topic could be an article in itself), I think it's largely dependant upon the project at hand, along with the environment in which the development will take place. There's definitely a place for it for large scale web applications, and I look forward to giving AMD a test run at some point.

Regards,

Joe

Comment: 9

can you provide some examples or best practices for accessing and changing the dom in bulk?

Comment: 10

A few of us have put together a summary of the many errors, omissions, and misleading statements in this article. You can view it here: https://gist.github.com/3086328. As we say there, we'd encourage you to retract this article.

Comment: 11

All the items here are either irrelevant or wrong. To take just one example, variable declarations inside a loop exact no performance penalty, since such declarations are "hoisted".

Comment: 12

Thank you for the feedback and taking the time to comment rmuphey, I will revise in light of the information gathered.

Comment: 13

It's not a crappy low quality filler article! It's a test to see if you spotted all the horrible mistakes.

Comment: 14

Also I tried flagging the article but the Report Abuse link only shows for comments
July issue on sale now!

The Week in Web Design

Sign up to our 'Week in Web Design' newsletter!

Hosting Directory
.net digital edition
Treat yourself to our geeky merchandise!
site stat collection