O'Reilly logo

Supercharged JavaScript Graphics by Raffaele Cecco

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 1. Code Reuse and Optimization

JavaScript has an undeservedly dubious reputation. Many people have written about its limitations as an object-oriented programming (OOP) language, even questioning whether JavaScript is an OOP language at all (it is). Despite JavaScript’s apparent syntactic resemblance to class-based OOP languages like C++ and Java, there is no Class statement (or equivalent) in JavaScript, nor any obvious way to implement popular OOP methodologies such as inheritance (code reuse) and encapsulation. JavaScript is also very loosely typed, with no compiler, and hence offers very few errors or warnings when things are likely to go wrong. The language is too forgiving in almost all instances, a trait that gives unsuspecting programmers a huge amount of freedom on one hand, and a mile of rope with which to hang themselves on the other.

Programmers coming from more classic and strictly defined languages can be frustrated by JavaScript’s blissful ignorance of virtually every programming faux pas imaginable: global functions and variables are the default behavior, and missing semicolons are perfectly acceptable (remember the rope mentioned in the previous paragraph?). Of course, any frustration is probably due to a misunderstanding of what JavaScript is and how it works. Writing JavaScript applications is much easier if programmers first accept a couple of foundational truths:

  • JavaScript is not a class-based language.

  • Class-based OOP is not a prerequisite for writing good code.

Some programmers have attempted to mimic the class-based nature of languages like C++ in JavaScript, but this is analogous to pushing a square peg into a round hole: it can be done (sort of), but the end result can feel contrived.

No programming language is perfect, and one could argue that the perceived superiority of certain programming languages (or indeed, the perceived superiority of OOP itself) is a good example of the emperor’s new clothes.[1]In my experience, software written in C++, Java, or PHP generates no fewer bugs or problems than projects created with JavaScript. In fact (cautiously sticking my neck out), I might suggest that due to JavaScript’s flexible and expressive nature, you can develop projects in it more quickly than in other languages.

Luckily, most of JavaScript’s shortcomings can be mitigated, not by forcibly contorting it into the ungainly imitation of another language, but by taking advantage of its inherent flexibility while avoiding the troublesome bits. The class-based nature of other languages can be prone to unwieldy class hierarchies and verbose clumsiness. JavaScript offers other inheritance patterns that are equally useful, but lighter-weight.

If there are many ways to skin a cat, there are probably even more ways to perform inheritance in JavaScript, given its flexible nature. The following code uses prototypal inheritance to create a Pet object and then a Cat object that inherits from it. This kind of inheritance pattern is often found in JavaScript tutorials and might be regarded as a “classic” JavaScript technique:

// Define a Pet object. Pass it a name and number of legs.
var Pet = function (name, legs) {
    this.name = name; // Save the name and legs values.
    this.legs = legs;
};

// Create a method that shows the Pet's name and number of legs.
Pet.prototype.getDetails = function () {
    return this.name + ' has ' + this.legs + ' legs';
};

// Define a Cat object, inheriting from Pet.
var Cat = function (name) {
    Pet.call(this, name, 4); // Call the parent object's constructor.
};

// This line performs the inheritance from Pet.
Cat.prototype = new Pet();

// Augment Cat with an action method.
Cat.prototype.action = function () {
    return 'Catch a bird';
};

// Create an instance of Cat in petCat.
var petCat = new Cat('Felix');

var details = petCat.getDetails();    // 'Felix has 4 legs'.
var action = petCat.action();         // 'Catch a bird'.
petCat.name = 'Sylvester';            // Change petCat's name.
petCat.legs = 7;                      // Change petCat's number of legs!!!
details = petCat.getDetails();        // 'Sylvester has 7 legs'.

The preceding code works, but it’s not particularly elegant. The use of the new statement makes sense if you’re accustomed to other OOP languages like C++ or Java, but the prototype keyword makes things more verbose, and there is no privacy; notice how petCat has its legs property changed to a bizarre value of 7. This method of inheritance offers no protection from outside interference, a shortcoming that may be significant in more complex projects with several programmers.

Another option is not to use prototype or new at all and instead take advantage of JavaScript’s ability to absorb and augment instances of objects using functional inheritance:

// Define a pet object. Pass it a name and number of legs.
var pet = function (name, legs) {
    // Create an object literal (that). Include a name property for public use
    // and a getDetails() function. Legs will remain private.
    // Any local variables defined here or passed to pet as arguments will remain
    // private, but still be accessible from functions defined below.
    var that = {
        name: name,
        getDetails: function () {
            // Due to JavaScript's scoping rules, the legs variable
            // will be available in here (a closure) despite being
            // inaccessible from outside the pet object.
            return that.name + ' has ' + legs + ' legs';
        }
    };
    return that;
};

// Define a cat object, inheriting from pet.
var cat = function (name) {
    var that = pet(name, 4); // Inherit from pet.
    // Augment cat with an action method.
    that.action = function () {
        return 'Catch a bird';
    };
    return that;
};

// Create an instance of cat in petCat2.
var petCat2 = cat('Felix');

details = petCat2.getDetails();   // 'Felix has 4 legs'.
action = petCat2.action();        // 'Catch a bird'.
petCat2.name = 'Sylvester';       // We can change the name.
petCat2.legs = 7;                 // But not the number of legs!
details = petCat2.getDetails();   // 'Sylvester has 4 legs'.

There is no funny prototype business here, and everything is nicely encapsulated. More importantly, the legs variable is private. Our attempt to change a nonexistent public legs property from outside cat simply results in an unused public legs property being created. The real legs value is tucked safely away in the closure created by the getDetails() method of pet. A closure preserves the local variables of a function—in this case, pet()—after the function has finished executing.

In reality, there is no “right” way of performing inheritance with JavaScript. Personally, I find functional inheritance a very natural way for JavaScript to do things. You and your application may prefer other methods. Look up “JavaScript inheritance” in Google for many online resources.

Note

One benefit of using prototypal inheritance is efficient use of memory; an object’s prototype properties and methods are stored only once, regardless of how many times it is inherited from.

Functional inheritance does not have this advantage; each new instance will create duplicate properties and methods. This may be an issue if you are creating many instances (probably thousands) of large objects and are worried about memory consumption. One solution is to store any large properties or methods in an object and pass this as an argument to the constructor functions. All instances can then utilize the one object resource rather than creating their own versions.

Keeping It Fast

The concept of “fast-moving JavaScript graphics” may seem like an oxymoron.

Truth be told, although the combination of JavaScript and a web browser is unlikely to produce the most cutting-edge arcade software (at least for the time being), there is plenty of scope for creating slick, fast-moving, and graphically rich applications, including games. The tools available are certainly not the quickest, but they are free, flexible, and easy to work with.

As an interpreted language, JavaScript does not benefit from the many compile-time optimizations that apply to languages like C++. While modern browsers have improved their JavaScript performance enormously, there is still room to enhance the execution speed of applications. It is up to you, the programmer, to decide which algorithms to use, which code to improve, and how to manipulate the DOM in efficient ways. No robot optimizer can do this for you.

A JavaScript application that only processes the occasional mouse click or makes the odd AJAX call will probably not need optimization unless the code is horrendously bad. The nature of applications covered in this book requires efficient code to give the user a satisfactory experience—moving graphics don’t look good if they are slow and jerky.

The rest of this chapter does not examine the improvement of page load times from the server; rather, it deals with the optimization of running code that executes after the server resources have loaded. More specifically, it covers optimizations that will be useful in JavaScript graphics programming.

What and When to Optimize

Of equal importance to optimization is knowing when not to do it. Premature optimization can lead to cryptic code and bugs. There is little point in optimizing areas of an application that are seldom executed. It’s a good idea to use the Pareto principle, or 80–20 rule: 20% of the code will use 80% of the CPU cycles. Concentrate on this 20%, 10%, or 5%, and ignore the rest. Fewer bugs will be introduced, the majority of code will remain legible, and your sanity will be preserved.

Using profiling tools like Firebug will quickly give you a broad understanding of which functions are taking the most time to execute. It’s up to you to rummage around these functions and decide which code to optimize. Unfortunately, the Firebug profiler is available only in Firefox. Other browsers also have profilers, although this is not necessarily the case on older versions of the browser software.

Figure 1-1 shows the Firebug profiler in action. In the Console menu, select Profile to start profiling, and then select Profile again to stop profiling. Firebug will then display a breakdown of all the JavaScript functions called between the start and end points. The information is displayed as follows:

Function

The name of the function called

Percent

Percentage of total time spent in the function

Call

How many times the function was called

Own time

Time spent within a function, excluding calls to other functions

Time

Total time spent within a function, including calls to other functions

Average

Average of Own times

Min

Fastest execution time of function

Max

Slowest execution time of function

File

The JavaScript file in which the function is located

Firebug profiler in action
Figure 1-1. Firebug profiler in action

Being able to create your own profiling tests that work on all browsers can speed up development and provide profiling capabilities where none exist. Then it is simply a matter of loading the same test page into each browser and reading the results. This is also a good way of quickly checking micro-optimizations within functions. Creating your own profiling tests is discussed in the upcoming section Homespun Code Profiling.

Warning

Debuggers like Firebug can skew timing results significantly. Always ensure that debuggers are turned off before performing your own timing tests.

“Optimization” is a rather broad term, as there are several aspects to a web application that can be optimized in different ways:

The algorithms

Does the application use the most efficient methods for processing its data? No amount of code optimization will fix a poor algorithm. In fact, having the correct algorithm is one of the most important factors in ensuring that an application runs quickly, along with the efficiency of DOM manipulation.

Sometimes a slow, easy-to-program algorithm is perfectly adequate if the application makes few demands. In situations where performance is beginning to suffer, however, you may need to explore the algorithm being used.

Examining the many different algorithms for common computer science problems such as searching and sorting is beyond the scope of this book, but these subjects are very well documented both in print and online. Even more esoteric problems relating to 3D graphics, physics, and collision detection for games are covered in numerous books.

The JavaScript

Examine the nitty-gritty parts of the code that are called very frequently. Executing a small optimization thousands of times in quick succession can reap benefits in certain key areas of your application.

The DOM and jQuery

DOM plus jQuery can equal a brilliantly convenient way of manipulating web pages. It can also be a performance disaster area if you fail to observe a few simple rules. DOM searching and manipulation are inherently slow and should be minimized where possible.

Homespun Code Profiling

The browser environment is not conducive to running accurate code profiling. Inaccurate small-interval timers, demands from events, sporadic garbage collection, and other things going on in the system all conspire to skew results. Typically, JavaScript code can be profiled like this:

var startTime = new Date().getTime();
// Run some test code here.
var timeElapsed = new Date().getTime() - startTime;

Although this approach would work under perfect conditions, for reasons already stated, it will not yield accurate results, especially where the test code executes in a few milliseconds.

A better approach is to ensure that the tests run for a longer period of time—say, 1,000 milliseconds—and to judge performance based on the number of iterations achieved within that time. Run the tests several times so you can perform statistical calculations such as mean and median.

To ensure longer-running tests, use this code:

// Credit: based on code by John Resig.

var startTime = new Date().getTime();
for (var iters = 0; timeElapsed < 1000; iters++) {
   // Run some test code here.
   timeElapsed = new Date().getTime() - startTime;
}
// iters = number of iterations achieved in 1000 milliseconds.

Regardless of the system’s performance, the tests will run for the same amount of time. Very fast systems will simply achieve more iterations. In practice, this method returns nicely consistent results.

The profiling tests in this chapter run each test five times, for 1,000 milliseconds each. The median number of iterations is then used as the final result.

Optimizing JavaScript

Strictly speaking, many optimizations that can be applied to JavaScript can be applied to any language. Going down to the CPU level, the rule is the same: minimize work. In JavaScript, the CPU-level work is so abstracted from the programmer that it can be difficult to ascertain how much work is actually going on. If you use a few tried-and-tested methods, it is a safe bet that your code will benefit, although only performing empirical tests will prove this conclusively.

Lookup Tables

Computationally expensive calculations can have their values precalculated and stored in a lookup table. You can then quickly pull the values out of the lookup table using a simple integer index. As long as accessing a value from the lookup table is a cheaper operation than calculating the value from scratch, an application will benefit from better performance. JavaScript’s trigonometry functions are a good example of where you can use lookup tables to speed things up. In this section, the Math.sin() function will be superseded by a lookup table, and we’ll build an animated graphical application to utilize it.

The Math.sin() function accepts a single argument: an angle, measured in radians. It returns a value between −1 and 1. The angle argument has an effective range of 0 to 2π radians, or about 6.28318. This is not very useful for indexing into a lookup table, as the range of just six possible integer values is too small. The solution is to dispense with radians completely and allow the lookup table to accept integer indexes of between 0 and 4,095. This granularity should be enough for most applications, but you can make it finer by specifying a larger steps argument:

var fastSin = function (steps) {
    var table = [],
        ang = 0,
        angStep = (Math.PI * 2) / steps;
    do {
        table.push(Math.sin(ang));
        ang += angStep;
    } while (ang < Math.PI * 2);
    return table;
};

The fastSin() function divides 2π radians into the number of steps specified in the argument, and stores the sin values for each step in an array, which is returned.

Testing the JavaScript Math.sin() against a lookup table yields the results shown in Figure 1-2.

Math.sin() versus lookup table performance. Bigger is better.
Figure 1-2. Math.sin() versus lookup table performance. Bigger is better.

Across most browsers, there appears to be an approximately 20% increase in performance, with an even more pronounced improvement in Google Chrome. If the calculated values within the lookup table had come from a more complex function than Math.sin(), then the performance gains would be even more significant; the speed of accessing the lookup table remains constant regardless of the initial work required to fill in the values.

The following application uses the fastSin() lookup table to create a hypnotic animated display. Figure 1-3 shows the output.

<!DOCTYPE html>
<html>

    <head>
        <title>
            Fast Sine Demonstration
        </title>
        <script type="text/javascript"
            src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
        </script>
        <style type="text/css">
            #draw-target {
                width:480px; height:320px;
                background-color:#000; position:relative;
            }
        </style>
        <script type="text/javascript">
            $(document).ready(function() {
                (function() {
                    var fastSin = function(steps) {
                        var table = [],
                            ang = 0,
                            angStep = (Math.PI * 2) / steps;
                        do {
                            table.push(Math.sin(ang));
                            ang += angStep;
                        } while (ang < Math.PI * 2);
                        return table;
                    };
Sine lookup table used in an animated application
Figure 1-3. Sine lookup table used in an animated application

The fastSin() function is called, and the created sine lookup table is referenced in sinTable[].

                    var sinTable = fastSin(4096),
                        $drawTarget = $('#draw-target'),
                        divs = '',
                        i, bars, x = 0;

The drawGraph() function draws a sine wave by updating the height and position of numerous one-pixel-wide divs. Table 1-1 shows the arguments.

                    var drawGraph = function(ang, freq, height) {
                        var height2 = height * 2;
                        for (var i = 0; i < 480; i++) {
                            bars[i].style.top =
                            160 - height + sinTable[(ang + (i * freq)) & 4095]
                                * height + 'px';
                            bars[i].style.height = height2 + 'px';
                        }
                    };
Table 1-1. Arguments passed to drawGraph()

Argument

Description

ang

The start angle for the sine wave.

freq

The frequency of the sine wave. Defines the “tightness” of the wave.

height

The height (amplitude) of the wave; also affects the thickness of the lines drawn.

The following loop creates 480 one-pixel vertical div elements. The divs are then appended to $drawTarget. All the divs are then referenced in the bars[] array for use in drawGraph().

                    for (i = 0; i < 480; i++) {
                        divs +=
                            '<div style = "position:absolute;width:1px;height:40px;'
                            + 'background-color:#0d0; top:0px; left: '
                            + i + 'px;"></div>';
                    }
                    $drawTarget.append(divs);
                    bars = $drawTarget.children();

setInterval() repeatedly calls drawGraph() with continuously changing parameters to create an animated effect:

                    setInterval(function() {
                        drawGraph(x * 50, 32 - (sinTable[(x * 20) & 4095] * 16),
                            50 - (sinTable[(x * 10) & 4095] * 20));
                        x++;
                    }, 20);
                })();

            });
        </script>
    </head>

    <body>
        <div id="draw-target">
        </div>
    </body>

</html>

Bitwise Operators, Integers, and Binary Numbers

In JavaScript, all numbers are represented in a floating-point format. In contrast to languages such as C++ and Java, int and float types are not explicitly declared. This is a surprising omission, and a legacy of JavaScript’s early years as a simple language intended for web designers and amateurs. JavaScript’s single number type does help you avoid many numeric type errors. However, integers are fast, CPU-friendly, and the preferred choice for many programming tasks in other languages.

Note

JavaScript’s number representation is defined in the ECMAScript Language Specification as “double-precision 64-bit format IEEE 754 values as specified in the IEEE Standard for Binary Floating-Point Arithmetic.” This gives a (somewhat huge) range of large numbers (±1.7976931348623157 × 10308) or small numbers (±5 × 10−324). Beware, though: floating-point numbers are subject to rounding errors; for example, alert(0.1 + 0.2) displays 0.30000000000000004, not 0.3 as expected!

However, a closer look at the ECMAScript Standard reveals that JavaScript has several internal operations defined to deal with integers:

ToInteger

Converts to an integer

ToInt32

Converts to a signed 32-bit integer

ToUint32

Converts to an unsigned 32-bit integer

ToUint16

Converts to an unsigned 16-bit integer

You cannot use these operations directly; rather, they are called under the hood to convert numbers into an appropriate integer type for JavaScript’s rarely used bitwise operators. Though sometimes incorrectly dismissed as slow and irrelevant to web programming, some of these operators possess quirky abilities that can be useful for optimization.

Warning

Bitwise operators convert numbers into 32-bit integers, with a numerical range of −2,147,483,648 to 2,147,483,647. Numbers outside this range will be adjusted to fit.

A quick recap of binary numbers

During the halcyon days of computing, when 16 KB of RAM was considered a lot, binary numbers were a programmer’s staple diet. The sort of low-level programming used for the computers of the day required a good understanding of binary and hexadecimal notation. Binary numbers are rarely used in web programming, but they still have their place in areas such as hardware drivers and networking.

Everyone is familiar with the base-10 number system. In the first row of Table 1-2, each column from right to left represents an increasing power of 10. By multiplying the numbers in the second row by their corresponding power of 10 and then adding all the results (or products) together, we end up with a final number:

(3 × 1,000) + (9 × 1) = 3,009
Table 1-2. Base-10 number system

10,000

1,000

100

10

1

0

3

0

0

9

The principle is exactly the same for base-2, or binary, numbers. However, instead of the columns increasing in powers of 10, they increase in powers of 2. The only digits required in the second row are either 0 or 1, also known as a bit. The simple on-off nature of binary numbers is perfect for emulating in digital electronic circuits. Table 1-3 shows the binary representation of the base-10 number 69:

(1 × 64) + (1 × 4) + (1 × 1) = 69
Table 1-3. The 8-bit binary representation of base-10 number 69

128

64

32

16

8

4

2

1

0

1

0

0

0

1

0

1

How can a binary number be negated (sign change)? A system called twos complement is used as follows:

  1. Invert each bit in the binary number, so 01000101 becomes 10111010.

  2. Add 1, so 10111010 becomes 10111011 (−69).

The topmost bit acts as a sign, where 0 means positive and 1 means negative. Go through the same procedure again, and we are back to +69.

JavaScript’s bitwise operators

JavaScript’s bitwise operators act on the binary digits, or bits, within an integer number.

Bitwise AND (x & y)

This performs a binary AND on the operands, where the resultant bit will be set only if the equivalent bit is set in both operands. So, 0x0007 & 0x0003 gives 0x0003. This can be a very fast way of checking whether an object possesses a desired set of attributes or flags. Table 1-4 shows the available flags for a pet object. For example, a small, old, brown dog would have a flags value of 64 + 16 + 8 + 2 = 90.

Table 1-4. Binary flags of a pet object

Big

Small

Young

Old

Brown

White

Dog

Cat

128

64

32

16

8

4

2

1

Searching for pets with certain flags is simply a case of performing a bitwise AND with a search value. The following code searches for any pet that is big, young, and white (it may be either a cat or dog, as this is not specified):

var searchFlags = 128 + 32 + 4;
var pets = []; // This is an array full of pet objects.
var numPets = pets.length;
for (var i = 0; i < numPets; i++) {
    if (searchFlags & pets[i].flags === searchFlags) {
        /* Found a Match! Do something. */
    }
}

With a total of 32 bits available in an integer to represent various flags, this can be much faster than checking flags stored as separate properties or other types of conditional testing; for example:

var search = ['big','young','white'};
var pets = []; // This is an array full of pet objects.
var numPets = pets.length;
for (var i = 0; i < numPets; i++) {
    // The following inner loop makes things much slower.
    for(var c=0;c<search.length;c++) {
        // Check if the property exists in the pet object.
        if ( pets[i][search[c]] == undefined) break;
    }
    if( c == search.length ) {
        /* Found a Match! Do something. */
    }
}

The & operator can also act in a similar way to the modulus operator (%), which returns the remainder after division. The following code will ensure that the variable value is always between 0 and 7:

value &= 7; // Equivalent to value % 8;

The equivalence to the % operator works only if the value after the & is 1, or a power of 2 less 1 (1, 3, 7, 15, 31...).

Bitwise OR (x | y)

This performs a binary OR on the operators, where the resultant bit will be set if the equivalent bit is set in either operand. So, 0x0007 | 0x0003 gives 0x0007. Effectively, it merges the bits together.

Bitwise XOR (x ^ y)

This performs a binary exclusive OR on the operators, where the resultant bit will be set if only one of the equivalent bits is set in either operand. So, 0x0000 ^ 0x0001 gives 0x0001, and 0x0001 ^ 0x0001 gives 0x0000. This can act as a shorthand way of toggling a variable:

toggle ^= 1;

Each time toggle ^= 1; is executed, the toggle value will flip between 1 and 0 (assuming it is 1 or 0 to start with). Here is the equivalent code using if-else:

if (toggle) {
    toggle = 0;
}else {
    toggle = 1;
}

or using the ternary operator (?):

toggle = toggle ? 0:1;
Bitwise NOT (~x)

This performs a ones complement, or inversion of all bits. So, in binary, 11100111 would become 00011000. If the number in question is a signed integer (where the topmost bit represents the sign), then the ~ operator is equivalent to changing the sign and subtracting 1.

Shift left (x << numBits)

This performs a binary shift left by a specified number of bits. All bits are moved to the left, the topmost bit is lost, and a 0 is fed into the bottommost bit. This is the equivalent of an unsigned integer multiplication of x by 2^numBits. Here are some examples:

y = 5 << 1; // y = 10; Equivalent to Math.floor(5 * (2^1)).
y = 5 << 2; // y = 20; Equivalent to Math.floor(5 * (2^2)).
y = 5 << 3; // y = 40; Equivalent to Math.floor(5 * (2^3)).

Tests reveal no performance benefit over using the standard multiply operator (*).

Shift right with sign (x >> numBits)

This performs a binary shift right by a specified number of bits. All bits are moved to the right, with the exception of the topmost bit, which is preserved as the sign. The bottommost bit is lost. This is the equivalent of a signed integer division of x by 2^numBits. Here are some examples:

y = 10 >> 1; // y = 5; Equivalent to Math.floor(5 / (2^1)).
y = 10 >> 2; // y = 2; Equivalent to Math.floor(5 / (2^2)).
y = 10 >> 3; // y = 1; Equivalent to Math.floor(5 / (2^3)).

Tests reveal no performance benefit over using the standard divide operator (/).

The following code looks pretty useless:

x = y >> 0;

However, it forces JavaScript to call its internal integer conversion functions, resulting in the fractional parts of the number being lost. Effectively, it is performing a fast Math.floor() operation. Figure 1-4 shows that for Internet Explorer 8, Google Chrome, and Safari 5.0, there is a speed increase.

Shift right with zero fill (x >>> y)

Rarely used, this is similar to the >> operator, but the topmost bit (sign bit) is not preserved and is set to 0. The bottommost bit is lost. For positive numbers, this is the same as the >> operator. For negative numbers, however, the result is a positive number. Here are some examples:

y = 10 >>> 1; // y = 5;
y = −10 >>> 2; // y = 1073741821;
y = −10 >>> 3; // y = 536870910;
Math.floor() versus bitshift. Bigger is better.
Figure 1-4. Math.floor() versus bitshift. Bigger is better.

Loop unrolling: An inconvenient truth

Looping in any programming language adds a certain amount of overhead beyond the code within the loop. Loops usually maintain a counter and/or check for the termination condition, both of which take time.

Removing the loop overhead provides some performance benefits. A typical JavaScript loop looks like this:

for (var i = 0; i < 8; i++) {
    /*** do something here  **/
}

By executing this instead, you can completely eliminate the loop overhead:

/*** do something here  ***/
/*** do something here  ***/
/*** do something here  ***/
/*** do something here  ***/
/*** do something here  ***/
/*** do something here  ***/
/*** do something here  ***/
/*** do something here  ***/

However, with a loop of just eight iterations, the improvement is not worth the effort. Assuming do something here is a simple statement (e.g., x++), removing the loop might execute the code 300% faster, but this is at the microsecond level; 0.000003 seconds versus 0.000001 seconds is not going to make a noticeable difference. If do something here is a big and slow function call, then the figures read more like 0.100003 seconds versus 0.100001 seconds. Again, too small an improvement to be worthwhile.

There are two factors that determine whether loop unrolling will provide a tangible benefit:

  • The number of iterations. In practice, many iterations (probably thousands) are needed to make a difference.

  • The proportion of time the inner loop code takes versus the loop overhead. Complex inner loop code that is many times slower to execute than the loop overhead will show a smaller improvement. This is because most of the time is being spent inside the inner loop code, not the loop overhead.

It is not practical to entirely unroll loops that require hundreds or thousands of iterations. The solution is to use a technique that is a variation of Duff’s device. This works by performing partial unrolling of a loop. For example, a loop of 1,000 iterations can be broken into 125 iterations of code that is unrolled eight times:

// Credit: from Jeff Greenberg's site via an anonymous donor.
var testVal = 0;
var n = iterations % 8
while (n--)
{
    testVal++;
}

n = parseInt(iterations / 8);
while (n--)
{
    testVal++;
    testVal++;
    testVal++;
    testVal++;
    testVal++;
    testVal++;
    testVal++;
    testVal++;
  }
}

The first while loop takes into account situations where the number of iterations is not divisible by the number of unrolled code lines. For example, 1,004 iterations requires a loop of 4 normal iterations (1004 % 8), followed by 125 unrolled iterations of 8 each (parseInt(1004 / 8)). Here is a slightly improved version:

var testVal = 0;
var n = iterations >> 3; // Same as: parseInt(iterations / 8).
while(n--){
    testVal++;
    testVal++;
    testVal++;
    testVal++;
    testVal++;
    testVal++;
    testVal++;
    testVal++;
}
n = iterations - testVal; // testVal has kept a tally, so do the remainder here.
while(n--) {
    testVal++;
}

Note

Duff’s device refers to a specific C-language optimization for unrolling loops that was developed by Tom Duff in 1983. He does not claim credit for loop unrolling as a general principle. Loop unrolling is common practice in assembly language, where tiny optimizations can make a difference in areas such as large memory copies and clears. Optimizing compilers may also perform automatic loop unrolling.

For loops of 10,000 iterations with trivial code, this returns significant performance gains. Figure 1-5 shows the results. Should we now optimize all our loops like this? No, not yet. The test is unrealistic: it is unlikely that incrementing a local variable is all we want to do within a loop.

Unrolled loop with trivial inner loop code (10,000 iterations). Great results, but don’t get too excited. Bigger is better.
Figure 1-5. Unrolled loop with trivial inner loop code (10,000 iterations). Great results, but don’t get too excited. Bigger is better.

A better test involves iterating through an array and calling a function with the array contents. This is much more along the lines of what will happen inside real applications:

// Initialize 10000 items.
var items = [];
for (var i = 0; i < 10000; i++) {
    items.push(Math.random());
}

// A function to do some useful work.
var processItem = function (x) {
    return Math.sin(x) * 10;
};

// The slow way.
var slowFunc = function () {
    var len = items.length;
    for (var i = 0; i < len; i++) {
        processItem(items[i]);
    }
};

// The 'fast' way.
var fastFunc = function () {
    var idx = 0;
    var i = items.length >> 3;
    while (i--) {
        processItem(items[idx++]);
        processItem(items[idx++]);
        processItem(items[idx++]);
        processItem(items[idx++]);
        processItem(items[idx++]);
        processItem(items[idx++]);
        processItem(items[idx++]);
        processItem(items[idx++]);
    }
    i = items.length - idx;
    while (i--) {
        processItem(items[idx++]);
    }
};

Figure 1-6 shows the improvement. Ouch. The real work within the loops has made the loop unrolling benefit a drop in the ocean. It is somewhat akin to ordering the 4,000-calorie Mega Burger Meal and hoping the diet soda will make things less fattening. Considering the 10,000 iterations, this is a disappointing set of results.

Unrolled loop with nontrivial inner loop code (10,000 iterations). A disappointing turnout. Bigger is better.
Figure 1-6. Unrolled loop with nontrivial inner loop code (10,000 iterations). A disappointing turnout. Bigger is better.

The moral of this story is that JavaScript loops are actually rather efficient, and you need to place micro-optimizations within the context of real application behavior to realistically test their benefits.

Optimizing jQuery and DOM Interaction

jQuery is an extensively used JavaScript library and provides a concise, convenient, and flexible way of accessing and manipulating elements within the DOM. It is designed to mitigate cross-browser issues, allowing you to concentrate on core application development rather than fiddling with browser quirks. jQuery is built around a selector engine, which allows you to find DOM elements using familiar CSS-style selector statements. For instance, the following code returns a jQuery object (a kind of array) containing all image elements with a CSS class of big:

$images = jQuery('img.big');

or the jQuery shorthand notation way:

$images = $('img.big');

The $images variable is just that, a normal variable. The preceding $ is simply a reminder that it references a jQuery object.

There is one caveat to jQuery’s power: an apparently small and innocuous jQuery statement can do a lot of work behind the scenes. This might not be significant if a small number of elements is being accessed only occasionally. However, if many elements are being accessed on a continuous basis—for example, in a highly animated page—there can be serious performance implications.

Optimizing CSS Style Changes

A fundamental part of creating JavaScript graphics using DHTML is being able to quickly manipulate the CSS style properties of DOM elements. In jQuery, you can do this like so:

$('#element1').css('color','#f00');

This would find the element whose id is element1 and change its CSS color style to red.

Scratching beneath the surface, there is a lot going on here:

  • Make a function call to jQuery and ask it to search the DOM for an element with id of element1. Apart from doing the search itself, this involves performing regular expression tests to determine the type of search required.

  • Return the list of items found (in this case, one item) as a special jQuery array object.

  • Make a function call to the jQuery css() function. This performs various checks such as determining whether it is reading or writing a style, whether it is being passed a string argument or object literal, and more. It finally updates the style of the element itself.

Performing this type of work many times in succession will be slow, regardless of how efficient jQuery is under the hood:

$('#element1').css('color','#f00');    // Make red.
$('#element1').css('color','#0f0');    // Make green.
$('#element1').css('color','#00f');    // Make blue.
$('#element1').css('left,'100px');     // Move a bit.

Each of the preceding lines performs a search for the element with id of element1. Not good.

A faster method is to specify a context within which jQuery should search for elements. By default, jQuery begins its searches from the document root, or the topmost level within the DOM hierarchy. In many instances, starting from the root level is unnecessary and makes jQuery do more searching than is required. When you specify a context, jQuery has less searching to do and will return its results in less time.

The following example searches for all elements with a CSS class of alien, beginning the search within the DOM element referenced in container (the context):

$aliens = $('.alien', container); // Search within a specific DOM element.

The context parameter type is flexible and could have been another jQuery object or CSS selector:

// Start search within the elements of the jQuery object, $container.
$aliens = $('.alien', $container);

// Look for an element with id of 'container' and start the search there.
$aliens = $('.alien', '#container');

Make sure that searching for the context is not slower than searching for the elements within it! It is better to reference the context DOM element directly where possible.

Ideally, once elements have been found, you should not search for them again at all. We can cache (reuse) the search results instead:

var $elem = $('#element1');    // Cache the search results.
$elem.css('color','#f00');     // Make red.
$elem.css('color','#0f0');     // Make green.
$elem.css('color','#00f');     // Make blue.
$elme.css('left,'100px');      // Move a bit.

This still leaves the jQuery css() function call, which is doing more work than is necessary for our purposes. We can dereference the jQuery search results right down to the actual style object of the DOM element:

// Get the first element ([0]) from the jQuery search results and store
// a reference to the style object of that element in elemStyle.

var elemStyle = $('#element1')[0].style;

// It is now quicker to manipulate the CSS styles of the element.
// jQuery is not being used at all here:

elemStyle.color = '#f00';       // Make red.
elemStyle.color = '#0f0';       // Make green.
elemStyle.color = '#00f';       // Make blue.
elemStyle.left = '100px';       // Move a bit.

Figure 1-7 shows the performance results of setting a CSS style for one DOM element via an uncached jQuery.css(), a cached jQuery.css(), or a direct write to the style object of the DOM element. The differences would be even more significant in more complex pages with slower CSS selectors—for example, $('.some-css-class').

Where speed is of the essence, manipulating an element’s properties directly will be faster than going through jQuery. For example, the jQuery.html() method can be considerably slower than using an element’s innerHTML object directly.

Speed comparison of using uncached jQuery, cached jQuery, and direct write to update an element’s CSS style. Bigger is better.
Figure 1-7. Speed comparison of using uncached jQuery, cached jQuery, and direct write to update an element’s CSS style. Bigger is better.

Do the results in Figure 1-7 imply that we shouldn’t be using jQuery at all? Not so; jQuery is far too good a library to reject, and it is understandably slow in certain circumstances. The rule is to be wary of how jQuery is used in time-critical areas of your application. This will usually be a small percentage of the total code. The majority of your application can and should use jQuery for quicker development, convenience, and fewer cross-browser issues.

Optimizing DOM Insertion

If you need to add a large number of elements into the DOM in your application, there can be performance implications. The DOM is a complex data structure that prefers being left alone. Of course, this is not really feasible in dynamic web pages, so you need an efficient way of inserting elements.

You can insert an element into the DOM with jQuery like this:

$('#element1').append('<p>element to insert</p>');

This is perfectly adequate for a few elements, but when you need to add hundreds or thousands of elements, inserting them individually can be too slow.

A better way is to build up all the intended elements into one big string and insert them simultaneously as a single unit. For each element, this prevents the overhead of the jQuery call and the various internal tests it performs:

var    elements = '';

// First build up a string containing all the elements.
for (var i = 0; i < 1000; i++) {
    elements += '<p>This is element ' + i + '</p>';
}

// They can now be inserted all at once.
$('#element1').append(elements);

Other Resources

Here’s some suggested reading for those wanting to expand their knowledge of JavaScript:

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required