Make the Web Faster

Optimizing JavaScript code

Authors: Gregory Baker, Software Engineer on GMail & Erik Arvidsson, Software Engineer on Google Chrome

Recommended experience: Working knowledge of JavaScript

Client-side scripting can make your application dynamic and active, but the browser's interpretation of this code can itself introduce inefficiencies, and the performance of different constructs varies from client to client. Here we discuss a few tips and best practices to optimize your JavaScript code.

Working with strings

String concatenation causes major problems with Internet Explorer 6 and 7 garbage collection performance. Although these issues have been addressed in Internet Explorer 8 -- concatenating is actually slightly more efficient on IE8 and other non-IE browsers such as Chrome -- if a significant portion of your user population uses Internet Explorer 6 or 7, you should pay serious attention to the way you build your strings.

Consider this example:

var veryLongMessage =
'This is a long string that due to our strict line length limit of' +
maxCharsPerLine +
' characters per line must be wrapped. ' +
percentWhoDislike +
'% of engineers dislike this rule. The line length limit is for ' +
' style purposes, but we don't want it to have a performance impact.' +
' So the question is how should we do the wrapping?';

Instead of concatenation, try using a join:

var veryLongMessage =
['This is a long string that due to our strict line length limit of',
maxCharsPerLine,
' characters per line must be wrapped. ',
percentWhoDislike,
'% of engineers dislike this rule. The line length limit is for ',
' style purposes, but we don't want it to have a performance impact.',
' So the question is how should we do the wrapping?'
].join();

Similarly, building up a string across conditional statements and/or loops by using concatenation can be very inefficient. The wrong way:

var fibonacciStr = 'First 20 Fibonacci Numbers
';
for (var i = 0; i < 20; i++) {
fibonacciStr += i + ' = ' + fibonacci(i) + '
';
}

The right way:

var strBuilder = ['First 20 fibonacci numbers:'];
for (var i = 0; i < 20; i++) {
  strBuilder.push(i, ' = ', fibonacci(i));
}
var fibonacciStr = strBuilder.join('');

Building strings with portions coming from helper functions

Build up long strings by passing string builders (either an array or a helper class) into functions, to avoid temporary result strings.

For example, assuming buildMenuItemHtml_ needs to build up a string from literals and variables and would use a string builder internally, instead of using:

var strBuilder = [];
for (var i = 0, length = menuItems.length; i < length; i++) {
  strBuilder.push(this.buildMenuItemHtml_(menuItems[i]));
}
var menuHtml = strBuilder.join();

Use:

var strBuilder = [];
for (var i = 0, length = menuItems.length; i < length; i++) {
  this.buildMenuItem_(menuItems[i], strBuilder);
}
var menuHtml = strBuilder.join();

Defining class methods

The following is inefficient, as each time a instance of baz.Bar is constructed, a new function and closure is created for foo:

baz.Bar = function() {
  // constructor body
  this.foo = function() {
  // method body
  };
}

The preferred approach is:

baz.Bar = function() {
  // constructor body
};

baz.Bar.prototype.foo = function() {
  // method body
};

With this approach, no matter how many instances of baz.Bar are constructed, only a single function is ever created for foo, and no closures are created.

Initializing instance variables

Place instance variable declaration/initialization on the prototype for instance variables with value type (rather than reference type) initialization values (i.e. values of type number, Boolean, null, undefined, or string). This avoids unnecessarily running the initialization code each time the constructor is called. (This can't be done for instance variables whose initial value is dependent on arguments to the constructor, or some other state at time of construction.)

For example, instead of:

foo.Bar = function() {
  this.prop1_ = 4;
  this.prop2_ = true;
  this.prop3_ = [];
  this.prop4_ = 'blah';
};

Use:

foo.Bar = function() {
  this.prop3_ = [];
};

foo.Bar.prototype.prop1_ = 4;

foo.Bar.prototype.prop2_ = true;

foo.Bar.prototype.prop4_ = 'blah';

Avoiding pitfalls with closures

Closures are a powerful and useful feature of JavaScript; however, they have several drawbacks, including:

  • They are the most common source of memory leaks.
  • Creating a closure is significantly slower then creating an inner function without a closure, and much slower than reusing a static function. For example:

    function setupAlertTimeout() {
      var msg = 'Message to alert';
      window.setTimeout(function() { alert(msg); }, 100);
    }

    is slower than:

    function setupAlertTimeout() {
      window.setTimeout(function() {
        var msg = 'Message to alert';
        alert(msg);
      }, 100);
    }

    which is slower than:

    function alertMsg() {
      var msg = 'Message to alert';
      alert(msg);
    }

    function setupAlertTimeout() {
      window.setTimeout(alertMsg, 100);
    }
  • They add a level to the scope chain. When the browser resolves properties, each level of the scope chain must be checked. In the following example:

    var a = 'a';

    function createFunctionWithClosure() {
      var b = 'b';
      return function () {
        var c = 'c';
        a;
        b;
        c;
      };
    }

    var f = createFunctionWithClosure();
    f();

    when f is invoked, referencing a is slower than referencing b, which is slower than referencing c.

See IE+JScript Performance Recommendations Part 3: JavaScript Code inefficiencies for information on when to use closures with IE.

Avoiding with

Avoid using with in your code. It has a negative impact on performance, as it modifies the scope chain, making it more expensive to look up variables in other scopes.

Avoiding browser memory leaks

Memory leaks are an all too common problem with web applications, and can result in huge performance hits. As the memory usage of the browser grows, your web application, along with the rest of the user's system, slows down. The most common memory leaks for web applications involve circular references between the JavaScript script engine and the browsers' C++ objects' implementing the DOM (e.g. between the JavaScript script engine and Internet Explorer's COM infrastructure, or between the JavaScript engine and Firefox XPCOM infrastructure).

Here are some rules of thumb for avoiding memory leaks:

Use an event system for attaching event handlers

The most common circular reference pattern [ DOM element --> event handler --> closure scope --> DOM ] element is discussed in this MSDN blog post. To avoid this problem, use one of the well-tested event systems for attaching event handlers, such as those in Google doctype, Dojo, or JQuery.

In addition, using inline event handlers can lead to another kind of leak in IE. This is not the common circular reference type leak, but rather a leak of an internal temporary anonymous script object. For details, see the section on "DOM Insertion Order Leak Model" in Understanding and Solving Internet Explorer Leak Patterns and and an example in this JavaScript Kit tutorial.

Avoid expando properties

Expando properties are arbitrary JavaScript properties on DOM elements and are a common source of circular references. You can use expando properties without introducing memory leaks, but it is pretty easy to introduce one by accident. The leak pattern here is [ DOM element --> via expando--> intermediary object --> DOM element ]. The best thing to do is to just avoid using them. If you do use them, only use values with primitive types. If you do use non-primitive values, nullify the expando property when it is no longer needed. See the section on "Circular References" in Understanding and Solving Internet Explorer Leak Patterns.

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.