Monthly Archives: December 2007

Optimization Techniques For JavaScript

Often times JavaScript coding you’ll find yourself creating a loop and at some point in time doing some form of conditional statement. Conditions in loops have a heavy impact on the speed in which JavaScript will execute. Especially when the number of iterations is high. Take for example the following code segment:

var arr = new Array(); 
 
for(var i=0; i<1000; i++){
    if(document.all) // testing for IE
        arr.push("IE: " + i);
    else //mozilla
        arr.push("MO: " + i);
}//end for

The previous segment of code does nothing more than check the document.all property to detect if the browser is Internet Explorer (IE) otherwise, it assumes that it is a Mozilla flavored browser. The problem is, that simple if statement can slow things down quite a bit, especially if the detection is more involved. So how do we get around this? Sometimes, you don’t have a choice, sometimes you’re bound to evaluate within the loop, but if you can avoid it, it can really help speed up your JavaScript execution. Here’s one technique:
var arr = new Array(); 
 
//do browser detection first
if(document.all){ //testing for IE
    function push(a, i){
        a.push("IE: " + i);
    }//end push()
}
else{ //mozilla
    function push(a, i){
        a.push("MO: " + i);
    }//end push()
}//end if 
 
//now loop and always call the push function
for(var i=0; i<1000; i++){
    push(arr, i); //always calls the correct function
}//end for

Now, what we have done is detect the browser before the loop begins and have allowed a function to be created based on the detected browser. Regardless of the specific browser type, the “push” function will be created. Knowing this, we can simply call the “push” function within the loop. Believe it or not, this actually speeds things up considerably. In JavaScript, it takes far less time to just execute a function, than it does to evaluate a condition, even one as simple as this. To prove this, simple create a start and end date object before and after the loop for both code examples so far, then subtract the end time from the start time to see the difference. All good? Moving on to for loop conditioning…

The thing to watch out for is length properties, especially in Internet Explorer. Length properties are determined dynamically by querying the object they belong to. Why does this matter? Consider the following code:

//Assume there are 50 div elements on the current page 
 
var divs = document.getElementsByTagName("div"); //get all 50 div elements
for(var i=0; i<divs.length; i++){
    //do something
}//end for

The previous code is nice and clean right, should work well? The problem is that some browsers especially IE will query the length of the divs everything time the for loop condition executes. That’s right, that means for every single loop, the JavaScript engine is going to recount, item by item, the length of the array. This doesn’t happen for all browsers, some browsers are smart enough to query once and cache until something in the list changes. But to sure, we should code against it. The technique for this is simple:

//Assume there are 50 div elements on the current page 
 
var divs = document.getElementsByTagName("div"); //get all 50 div elements
var len = divs.length; //get length once and store it
for(var i=0; i<len; i++){
    //do something
}//end for

That simple change can really make or break the loop performance depending on the size of the array. For small arrays you probably won’t see very much in the way of improvement, but add 1000 divs and watch. Big difference! Now let’s look at dynamic node and HTML creation…

When it comes to creating dynamic HTML and inserting into the DOM, there are many techniques. This section specifically focus on optimization, getting more bang for buck, or uh…code. Consider the following code:

for(var i=0; i<100; i++){
    var el = document.createElement("div"); //create a DIV node
    document.body.appendChild(el); //append it to the body
    el.innerHTML = "DIV " + i; //give it some inner HTML to display
}//end for

This code is pretty simple and straight forward right? Create a div node, append it into the body of the document, add some text inside it, repeat. This seems like a pretty good object oriented way to go. Once the createElement call returns, you’ve got a real live DOM element to use. The problem here is, that simple createElement call is costly, quite a bit in fact. Now try the following code:

var divs = ""; //create an empty string to hold the divs
for(var i=0; i<100; i++){
    divs += "
DIV " + i + "
 
"; //append a div
}//end for
document.body.innerHTML = divs; //insert the divs

Wow, not a very object oriented approach. But it executes much faster than the previous code, why? Because of the overhead of calling the createElement and appendChild method. Creating and appending to a string object takes far less cycles to do, than to create objects in memory then append them. Now, if you really just can’t live without the object oriented approach to creating DOM elements, there is a middle ground. Now quite as slow as the first createElement example, but not as fast as the innerHTML way either. Here’s the middle ground:

var frag = document.createDocumentFragment(); //create a document fragment in memory to append the nodes to
for(var i=0; i<100; i++){
    var el = document.createElement("div"); //create a DIV node
    frag.appendChild(el); //append it to the document fragment
    el.innerHTML = "DIV " + i; //give it some inner HTML to display
}//end for
document.body.appendChild(frag); //now append all the nodes at once using the document fragment

This will help buy you a few cycles where performance is needed. However, keep in mind this is not the fastest way, just the fastest object oriented way using the createElement method. So, if you’re going to create just a few nodes, then going the object oriented createElement route isn’t too rough. But if you have a lot of nodes to add, you might want to consider building a string and using innerHTML to insert them into the DOM. Next let’s take a look at custom objects versus pseudo objects.

So now let’s look at custom objects versus pseudo objects. First let’s test with a simple custom object:

//Simple Object class
function SimpleObject(title, body){
    this.title = title;
    this.body = body; 
 
    return this;
}//end SimpleObject 
 
for(var i=0; i<10000; i++){
    var so = new SimpleObject("Object " + i, "Body of Object " + i);
}//end for

Now we have created a simple object, that has 2 public properties, title and body. We loop a few thousand times and create a few thousand objects. Seems appears to work decently, until you compare it with the following pseudo object way of doing it:

for(var i=0; i<10000; i++){
    var so = {title: "Object " + i, body: "Body of Object " + i};
}//end for

We now have created the pseudo object equivalent of the custom object example. The only difference is this one runs fast…I mean fast. The objects have the exact same public properties, title and body. They even contain the exact same data. But the pseudo objects take far less time and memory to create. So here the rule is simple, use pseudo objects where you can to optimize. For places where you can’t use a pseudo object, you can still optimize by use of the prototype property and pseudo object declaration. For example:

//Simple Object class
function SimpleObject(){
    return this;
}//end SimpleObject 
 
SimpleObject.prototype = {
    method1: function(){
        alert("method1");
    },
    method2: function(){
        alert("method2");
    }
};

So what we have done here, is use the standard class/function notation to declare our object. Next we use the declared object’s prototype to assign a pseudo object structure. This creates a hybrid of the two types previously discussed.

for(var i=0; i<10000; i++){
    var so = new SimpleObject();
}//end for

This approach gives you the benefit of object instanciation and the optimization of using pseudo structures. The main drawback to this approach is that all members of the defined pseudo object body are public in terms of scope. The same as they would be in a standard pseudo object.