[ Team LiB ] Previous Section Next Section

4.8 Improving Script Performance

NN 2, IE 3

4.8.1 Problem

You want to speed up a sluggish script.

4.8.2 Solution

When swallowing small doses of code, JavaScript interpreters tend to process data speedily. But if you throw a ton of complex and deeply nested code at a browser, you may notice some latency, even if all the data is downloaded in the browser.

Here are a handful of useful tips to help you unclog potential processing bottlenecks in your code:

  • Avoid using the eval( ) function

  • Avoid the with construction

  • Minimize repetitive expression evaluation

  • Use simulated hash tables for lookups in large arrays of objects

  • Avoid multiple document.write( ) method calls

Look for these culprits especially inside loops, where delays become magnified.

4.8.3 Discussion

One of the most inefficient functions in the JavaScript language is eval( ). This function converts a string representation of an object to a genuine object reference. It becomes a common crutch when you find yourself with a string of an object's name or ID, and need to build a reference to the actual object. For example, if you have a sequence of mouse rollover images comprising a menu, and their names are menuImg1, menuImg2, and so on, you might be tempted to create a function that restores all images to their normal image with the following construction:

for (var i = 0; i < 6; i++) {
    var imgObj = eval("document.menuImg" + i);
    imgObj.src="images/menuImg" + i + "_normal.jpg";
}

The temptation is there because you are also using string concatenation to assemble the URL of the associated image file. Unfortunately, the eval( ) function in this loop is very wasteful.

When it comes to referencing element objects, there is almost always a way to get from a string reference to the actual object reference without using the eval( ) function. In the case of images, the document.images collection (array) provides the avenue. Here is the revised, more streamlined loop:

for (var i = 0; i < 6; i++) {
    var imgObj = document.images["menuImg" + i];
    imgObj.src="images/menuImg" + i + "_normal.jpg";
}

If an element object has a name or ID, you can reach it through some collection that contains that element. The W3C DOM syntax for document.getElementById( ) is a natural choice when working in browsers that support the syntax and you have the element's ID as a string. But even for older code that supports names of things like images and form controls, there are collections to use, such as document.images and the elements collection of a form object (document.myForm.elements["elementName"]). For custom objects, see the later discussion about simulated hash tables. Hunt down every eval( ) function in your code and find a suitable, speedier replacement.

Another performance grabber is the with construction. The purpose of this control statement is to help narrow the scope of statements within a block. For example, if you have a series of statements that work primarily with a single object's properties and/or methods, you can limit the scope of the block so that the statements assume properties and methods belong to that object. In the following script fragment, the statements inside the block invoke the sort( ) method of an array and read the array's length property:

with myArray {
    sort( );
    var howMany = length;
}

Yes, it may look efficient, but the interpreter goes to extra lengths to fill in the object references before evaluating the nested expressions. Don't use this construction.

It takes processing cycles to evaluate any expression or reference. The more "dots" in a reference, the longer it takes to evaluate the reference. Therefore, you want to avoid repeating a lengthy object reference or expression if it isn't necessary, especially inside a loop. Here is fragment that may look familiar to you from your own coding experience:

function myFunction(elemID) {
    for (i = 0; i < document.getElementById(elemID).childNodes.length; i++) {
        if (document.getElementById(elemID).childNodes[i].nodeType =  = 1) {
            // processing element nodes here
        }
    }
}

In the course of this function's execution, the expression document.getElementById( ) evaluates twice as many times as there are child nodes in the element whose ID is passed to the function. At each start of the for loop's execution, the limit expression evaluates the method; then the nested if condition evaluates the same expression each time through the loop. More than likely, additional statements in the loop evaluate that expression to access a child node of the outer element object. This is very wasteful of processing time.

Instead, at the cost of one local variable, you can eliminate all of this repetitive expression evaluation. Evaluate the unchanging part just once, and then use the variable reference as a substitute thereafter:

function myFunction(elemID) {
    var elem = document.getElementById(elemID);
    for (i = 0; i < elem .childNodes.length; i++) {
        if (elem .childNodes[i].nodeType =  = 1) {
            // processing element nodes here
        }
    }
}

If all of the processing inside the loop is with only child nodes of the outer loop, you can further compact the expression evaluations:

function myFunction(elemID) {
    var elemNodes = document.getElementById(elemID).childNodes;
    for (i = 0; i < elemNodes.length; i++) {
        if (elemNodes[i].nodeType =  = 1) {
            // processing element nodes here
        }
    }
}

As an added bonus, you have also reduced the source code size. If you find instances of repetitive expressions whose values don't change during the course of the affected script segment, consider them candidates for pre-assignment to a local variable.

Next, eliminate time-consuming iterations through arrays, especially multidimensional arrays or arrays of objects. If you have a large array (say, more than about 100 entries), even the average lookup time may be noticeable. Instead, use the techniques shown in Recipe 3.9 to perform a one-time generation of a simulated hash table of the array. Assemble the hash table while the page loads so that any delay caused by creating the table is blended into the overall page-loading time. Thereafter, lookups into the array will be nearly instantaneous, even if the item found is the last item in the many-hundred member array.

The final tip addresses use of the document.write( ) method to generate content while the page loads. Treat this method as if it were an inherently slow input/output type of operation. Invoke the method as infrequently as possible. If you are writing a lot of content to the page, accumulate the HTML into a string variable, and blast it to the page with one call to document.write( ).

4.8.4 See Also

Recipe 3.9 for details on creating a simulated hash table from an array; Recipe 3.13 for a rare case where the eval( ) function can't be avoided.

    [ Team LiB ] Previous Section Next Section