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
get
Details()
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
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.
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; };
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 div
s. 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'; } };
Argument | Description |
| The start angle for the sine wave. |
| The frequency of the sine wave. Defines the “tightness” of the wave. |
| 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 div
s are then appended to $drawTarget
. All the div
s 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 |
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 |
How can a binary number be negated (sign change)? A system called twos complement is used as follows:
Invert each bit in the binary number, so 01000101 becomes 10111010.
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.
Searching for pet
s 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;
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.
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.
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
ofelement1
. 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.
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:
JavaScript: The Definitive Guide by David Flanagan (O’Reilly; http://oreilly.com/catalog/9780596101992)
JavaScript: The Good Parts by Douglas Crockford (O’Reilly; http://oreilly.com/catalog/9780596517748)
Get Supercharged JavaScript Graphics now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.