There is an exciting trend in the browser market today. A war is being fought over JavaScript performance. The browsers each seem to be trying to outdo each other with optimizing their JavaScript engines. Firefox has drastically improved their engine in the past few releases with SpiderMonkey and TraceMonkey, and Safari's SquirrelFish engine seemed to be unbelievably fast. That is, until Chrome's V8 hit the market. Take a look at some of these articles that show performance of some of these engines:

For complex JavaScript applications, much of the application’s performance is out of our hands as developers. It depends, in a large part, on the browser. However, there are some things we can do to make our JavaScript applications run smoothly and feel snappy regardless of the browser.

Programming JavaScript for performance

Many of the same performance techniques used in other languages such as C and Java can be used with JavaScript as well. Many articles have been written on how to optimize code in general. They all suggest excellent things such as making sure your algorithm is efficient, cache frequently used values, and unrolling loops. These tips can be applied to any language to improve performance.

The DOM

One unique thing with JavaScript is the DOM. The DOM (Document Object Model) is a representation of a web page in JavaScript objects. DOM manipulation is often a performance bottleneck for complex sites. This is because the DOM is more than just an object model, it represents the rendered page. Any changes to the DOM must also be reflected on the rendered page. Thus, DOM manipulation invokes dynamic, run-time rendering in the browser (sometimes called document reflow).

When manipulating the DOM, try creating an object model of what you want to display before injecting it into the DOM. That causes the browser to only invoke dynamic rendering once (when you insert the whole object model) rather than with each manipulation.

Compare these two MooTools examples which inject a couple elements dynamically.

Here’s an example of adding to the DOM immediately:

var div = new Element('div').inject(document.body);
new Element('p', {html: 'Hello'}).inject(div);
new Element('p', {html: 'World!'}).inject(div);

And here’s an example of waiting to add an element to the DOM:

var div = new Element('div');
new Element('p', {html: 'Hello'}).inject(div);
new Element('p', {html: 'World!'}).inject(div);
div.inject(document.body);

The code in Examples 1 and 2 are very similar with one key difference. Notice Example 1 injects div into document.body immediately. Now it’s part of the DOM. When the next line injects into div, it’s also injecting directly into the DOM, and the same with the next line. This causes 3 injections into the DOM and causes the browser to reflow 3 times.

The second example builds a sub-DOM first, and injects it once at the end. This small change can cause dramatic results. For this simple example, you would likely not notice anything. But I’ve worked on massive JavaScript rendered applications, and the difference between immediate and delayed injection is very noticeable.

Variable scope

In JavaScript, as with most other languages, variables can be accessed based on their scope. When a variable x is encountered, the compiler will start looking through the various scopes to find it, starting with the local scope and moving outward to the global scope. As such, one way to improve performance is to use the local scope as much as possible.

Since the local scope is searched first, if a variable exists in the local scope, the compiler doesn’t need to continue searching. This saves CPU cycles, and can be a big boost in performance-critical portions of code. Here’s an example:

Accessing global variables:

var global_counter = 1;
var increment() {
    for (var i=0; i < 100000; i++)
        global_counter++;
}

Caching global variables locally:

var global_counter = 1;
var increment = function() {
    var local_counter = global_counter;
    for (var i=0; i < 100000; i++)
        local_counter++;
    global_counter = local_counter;
}

While certainly not as elegant, Example 4 is faster because it removes 100,000 extra scope lookups by caching the global_counter in the local scope.

Closures

Another performance tip is to be very careful with closures. While closures are a beautiful part of the language, they are also a common source of memory leaks and slowdowns.

Closures make an outer local scope available to an inner function. Since JavaScript lets you pass around functions as variables, the outer scope is passed around as well. As long as the function stays around, the outer scope stays around too. Here’s an example:

var get_handler = function() {
    var x = 1;
    return function() {
            x++;
            alert(x);
        };
}

Here we have a function returning a function that makes use of a local variable, x. Normally, the local variable x would get garbage collected when get_handler() finishes executing. But this closure is holding a reference to that variable, preventing it from being cleaned up. This happens most frequently with event handlers, which are often created as closures. These event handlers often stay around for a very long time, gobbling up memory like an unsated lemure.

As with the DOM example, this simple example isn’t bad. But imagine this happening in every closure in a large JavaScript application. This shared scope can cause a massive drain on memory over the runtime life of the application.

Class methods

Often, object oriented programming is simulated in JavaScript using functions. This can be nice for those familiar with object oriented programming, but it imposes a structure that is not native to JavaScript. JavaScript is prototype-based. Rather than defining class methods at run-time, consider using the prototype property. Compare these two examples:

A simulated class:

var MyClass = function() {
    this.initialize = function() { /* ... */ },
    this.get_value = function() { /* ... */ }
};

Using JavaScript prototype

var MyClass = function() {};
MyClass.prototype.initialize = function() { /* ... */ };
MyClass.prototype.get_value = function() { /* ... */ };

In Example 6, every time a new instance of MyClass is created, initialize and get_value are set to new instances of their respective functions. This is a waste. Using the prototype property, we can add functions to MyClass once. When a new instance of MyClass is created, it inherits the properties of its prototype. It’s actually using the same instance of these functions, thus saving memory and the time it would take to instantiate the function instance.

Concluding thoughts

There are certainly hundreds more places where JavaScript performance can be improved. The few listed in this article are some of the most common pitfalls and can often provide the most bang for your buck. I highly recommend reading the pages in the “Further reading” section at the end of this article, as they provide much more thorough explanations and techniques for boosting performance in JavaScript

Further reading