If you are writing applications that have Web/cloud integration, respond to touch, and are responsive/adaptive, it's likely that you're writing at least some JavaScript code.
JavaScript is a powerful and flexible language. That power and flexibility comes at a price such that I must invoke the too often cited quote: “With power comes great responsibility.”
There are many challenges with JavaScript. One of the biggest challenges is the language itself. It's often misunderstood and misapplied. Although it has some trappings of an object-oriented language, it is first and foremost a functional language. In object orientation, inheritance, encapsulation, abstraction, and polymorphism are key concepts and largely, are your friend in that context. Whereas in object orientation, there are normally a strict set of operations that define a class, in the functional context, there are no such restrictions. In JavaScript, it's easy to dynamically add new operations to some object.
Those differences are not why JavaScript is selected. The reason is much more pragmatic in that JavaScript is supported by and has become the de facto standard language of the major browsers.
Differing browsers represent another set of challenges, as each has a different JavaScript Engine (V8 for Chrome, Chakra for IE, Carakan for Opera, and SpiderMonkey for FireFox). Performance considerations also have to take into account these different browsers and engines, although that analysis is beyond the scope of this article. In this article, I will present a number of guidelines that in general, will help make your JavaScript perform better. Like all guidelines, context matters and your mileage may vary. I will present these guidelines independent of any benchmark data. To run your own tests, there are a number of online JavaScript performance and testing environments. Two of the most popular are available on www.jsperf.com
and www.dromaeo.com
.
These tips for improving your JavaScript performance appear in no particular order.
Use Local Variables
In JavaScript, variables are resolved by referencing the scope chain. Local variables in JavaScript are always first in the chain. Global variables are next in line to be searched. Accordingly, local variables are faster to access. This means that it is often best to store a reference to global document object to a local variable instead of referencing the document object directly.
Of the following two code snippets, the second one performs faster because the global document variable is directly accessed pnly once.
//slower
var element1 = document.getElementById("username");
var element2 = document.getElementById("password");
//faster
var doc = document;
var element1 = doc.getElementById("username");
var element2 = doc.getElementById("password");
Remove Redundant DOM Queries, and Watch Your Use of jQuery and Other Libraries
You have likely viewed code like this:
//redundant DOM query
$("#elementid").click(function(){});
//some other actions
//then do something else with the DOM element
$("#elementid").hide();
In this example, I'm using jQuery (referenced by the $ variable), but the same idea applies if I were to use document.getElementById
or any other native JavaScript function. This tip relates to the first tip on using local variables.
In the previous example, there are two separate queries that need to be run in order to act on the same object. Also note that the same global object is referenced. Further, do you really need to use jQuery for something as simple as searching for a DOM object and attaching a handler? Not necessarily, but assuming that you have elected to standardize with jQuery, there are some simple things you can do to improve your code's performance:
The previous code can be optimized easily:
var _jquery = $;
var element = _jquery(_jquery("#elementid"));
element.click(function(){});
//other tasks
element.hide();
There are two optimizations. First, the global jQuery object is referenced once and is cached to a local variable. Remember, in the scope chain, local variables are searched first when resolving scope. The second optimization is to query the DOM once. Of course, I don't need to use jQuery at all:
var element = document.getElementById("elementid");
element.onclick = function(){};
//other tasks
element.style.display = "none";
Using jQuery doesn't always result in greater efficiency. That's not to say that you shouldn't use jQuery as jQuery is an indispensable tool that does a great job with abstracting away the browser. In other words, jQuery takes away the need to worry about idiosyncrasies among the various browsers. It also has a rich API that makes quick work of tasks that you would otherwise have to code yourself. Still, there are operations that are or are likely to be consistent across browsers. In all cases, moderation and pragmatism must prevail when using any JavaScript library.
When Referencing CSS, Favor Classes Over In-Line Styles
From the previous three code snippets, I want to focus on each snippet's last line of code:
$("#elementid").hide();
element.hide();
element.style.display = "none";
In all cases, the referenced element is hidden by setting the CSS display property to “none.” HTML uses a flow-based model for layout. When the DOM is manipulated, either by adding or removing elements or through affecting some attribute that alters the display, a process called reflow is initiated. The reflow process is exactly as the name implies, the layout (flow) is recalculated. These operations are expensive and often contribute greatly to poor website performance. In your experience, you may have encountered code blocks where the DOM is periodically manipulated. Each separate action causes a reflow operation. Enough of these, and you quickly begin to see degrading performance.
To counteract reflow denigration, whenever possible, favor applying and un-applying CSS. Reflows still occur but the process is much more efficient. In this case, a simple CSS class will do:
.hidden {
display: none
}
Instead of manipulating the styles individually, either apply or un-apply classes:
element.className = "hidden";
At Times, It's Best to Extend the Prototype
Consider this simple function (class) with one public method:
function someClass() {
var _somePrivateVariable = 0;
this.someMethod = function() {
}
}
Each time an instance of someClass
is created, a separate copy of someMethod
is also created. There's a cost, no matter how insignificant on an individual basis, each time an object is created in memory. Add these individual instances up, and the cost can be quite significant.
As previously mentioned, JavaScript is a prototype-based functional language. This is where JavaScript's power and flexibility become apparent. Functions in JavaScript are represented as objects in memory. What each object can do is first based on its prototype. Using someClass()
as an example, all instances share the same prototype. In other words, the prototype has one memory reference. The previous code could be re-written as follows:
function someClass() {
var _somePrivateVariable = 0;
}
someClass.prototype.someMethod = function(){};
No matter the number of someClass()
instances in memory, there is only one reference to someMethod
because it's attached directly to the prototype, of which there is only one memory reference.
I'm not suggesting that you always extend the prototype as a means to extend functionality. In cases where you have common functionality that needs to be shared across multiple functions, it may make sense to define the common functionality once and then apply it to the various prototypes. Your code will be more manageable, and if there are likely to be a lot of instances, the memory footprint will be greatly diminished and thereby performance will improve.
Avoid Direct Contact HTML Collections, Use Arrays Instead
The DOM document object contains a number of collections: forms, anchors, links, and images. These collections support functions such as getElementsByClassName
, getElementsByTagName
, etc. HTML collections are live, meaning that they are direct pointers to the underlying objects. Any changes to the collections are manifested in the HTML document itself. This is both a slow and dangerous process. To illustrate the problem, consider this canonical example:
var divElements = document.getElementsByTagName('div');
for (var item=0 i < divElements.length; item++ ) {
var divElement = document.createElement("div");
document.appendChild(divElement);
}
This loop will never end because the for loop is bounded by divElements.length
, which is indirectly altered by the call to appendChild
within the loop. Remember, HTML collections are live and therefore, are slower to access than a static, non-live alternative.
If you find that you need to work with HTML objects in your JavaScript, work with a copy instead of the live object.
var divElements = document.getElementsByTagName('div');
var divElementsArray = new Array();
divElementsArray.concat(divElements);
Alternatively, you could call the concat
method from the Array Prototype directly:
var divElements = document.getElementsByTagName('div');
var divElementsArray = Array.prototype.concat.call(divElements);
You can't get entirely away from HTML collections, but you can minimize your contact with them and thereby improve performance. Working with an array, as opposed to the live collection, can result in a 50 to 60-fold performance improvement.
Be Careful with Arrays
That's the fun thing with JavaScript, there's always something else lurking around the corner. As soon as somebody tells you about a cool tip or technique, the caveat to be careful often follows soon thereafter! Arrays can get quite complex with deep nesting levels. The farther into deep nesting levels you have to wade, the more your performance can degrade. If you find yourself working with an object contained in an array, you may be better off to reference that object with a separate local variable.
Instead of:
myArray[x].nestedArray[y].object.prop1;
myArray[x].nestedArray[y].object.prop2;
myArray[x].nestedArray[y].object.method();
Try:
var objectRef = myArray[x].nestedArray[y].object;
objectRef.prop1;
objectRef.prop2;
objectRef.method();
This tip relates to the first one on creating local variables. Creating local variables that shorten the scope chain to be searched for variable resolution is the single easiest way to improve JavaScript performance.
When Creating Functions, Decide First Whether a Literal Might Suffice
JavaScript is a functional language, and functions are at the heart of everything performed in JavaScript. But functions have a cost, both when declared and when executed. Most times, when deciding whether a function is required, the answer is likely to be yes. It's a useful exercise to scrutinize whether a particular artifact is required or not. To illustrate, consider this contrived example:
function add(x,y) {
return x+y;
}
var result = add(variableOne, variableTwo);
In practice, it's highly unlikely that anybody would create a function like this. The point here is what the function represents in JavaScript, not what this particular function does. Something like what follows would be much more efficient:
var result = variableOne + variableTwo;
The general guideline is to decide whether a separate function is warranted. If what you are trying to achieve is simple and narrow. such that there are native and more direct capabilities in JavaScript. and where there is little gained for reuse by hosting in a discrete function, then a separate function may not be the right alternative.
I'm not suggesting that performance considerations are the only metric to consider. There needs to be a balance between structure and performance. You may have to concede a little bit of performance to gain more maintainability and flexibility. Performance considerations must always be juxtaposed against context and pragmatism.
Loops: They All Are Comparable Except for the for in Loop
There are two loop constructs in JavaScript: for
and while
. There are two sub-types within each loop as well: for/for in
and while/do
. Whether you choose to use a for
, while
or do
loop, the performance is going to be consistent. The choice should be made solely on which loop construct best matches your particular use case.
The same cannot be said for the for in
loop. The for in
loop is a special for
loop to iterate over an object's properties. There is substantial overhead with the for in
loop. From a code economy standpoint, the for in
loop makes it easy to iterate over properties.
To achieve those economies, there are several operations that go on behind the scenes. JavaScript has to determine which properties to iterate over. The properties that can be iterated over are the ones where the Enumerable
attribute is true
. There is overhead in making the determination. Your code will perform better if instead, you use a regular for
loop to iterate over an array or collection. Another general rule is to never use a for in
loop to iterate over an array.
Take Advantage of Short Circuiting and Stack Your Expressions Optimally
This is simple tip to follow and applies to just about any programming environment. To illustrate, consider the following code:
var continue = myBoolFunction() && boolValue;
The continue Boolean variable is going to be either true
or false
. The way the expression is constructed, the function is always going to be executed. If the result is false
, the boolValue
variable won't be evaluated. Going back to what you know about local variables, switching the order will yield better performance:
var continue = boolValue && myBoolFunction();
Simple things like this, if given the proper attention, will yield better performance.
Creating local variables that shorten the scope chain is the single easiest way to improve JavaScript performance.
Not Everything Needs a Try Catch
The try catch finally
construct is important to use in cases where it is quite possible an exception will be encountered and you know how to process and handle those exceptions. A try catch
statement augments the scope chain and therefore, a new execution context is created.
The augmented scope chain and new execution context comes at a price. But this doesn't mean that you have to do without an error handler. The following is a simple way to create a global error handler:
window.onerror = function(msg, url, lineNumber) {
};
Going to 11, Avoid with() at All Costs
Like try catch
, the with()
statement augments the scope chain. The with()
statement is similar to Visual Basic's With / End With
statement:
with(obj) {
prop1 = 1;
prop2 = "test";
}
The code reads nicely. The side effects created by the with()
statement and the negative performance impact is not so nice. This last tip ends with where the first tip began; create a local variable instead:
var o = obj;
o.prop1 = 1;
o.prop2 = "test";
Conclusion
The tips presented in this article are generally accepted guidelines for optimal JavaScript performance. Like all guidelines, context around specific use cases must be considered. Code optimization is not a singular activity. Rather, it's an ongoing and incremental process.
In the heat of battle when deadlines are looming, the need to just “get it done” often outweighs performance concerns. As part of your Application Lifecycle Management (ALM) process, in addition to having JavaScript unit tests, be sure to have performance testing suites as well. Take time to periodically review your code to make sure that basic guidelines are followed. The tips in this article are not complicated and are easy to employ. For more reading on this topic, I encourage you to add the book High Performance JavaScript by Nicholas Zakas to your technical library.