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.
|