- Design Levels
- Measure Your Changes
- Algorithms and Data Structures
- Refactor to Simplify Code
- Minimize DOM Interaction and I/O
- Local Optimizations
- Expression Tuning
- Summary
Minimize DOM Interaction and I/O
Interacting with the DOM is significantly more complicated than arithmetic computations, which makes it slower. When the JavaScript interpreter encounters a scoped object, the engine resolves the reference by looking up the first object in the chain and working its way through the next object until it finds the referenced property. To maximize object resolution speed, minimize the scope chain of objects. Each node reference within an element's scope chain means more lookups for the browser. Keep in mind that there are exceptions, like the window object, which is faster to fully reference. So instead of this:
var link = location.href;
Do this:
var link = window.location.href;
Minimize Object and Property Lookups
Object-oriented techniques encourage encapsulation by tacking sub-nodes and methods onto objects. However, object-property lookups are slow, especially if there is an evaluation. So instead of this:
for(var i = 0; i < 1000; i++) a.b.c.d(i);
Do this:
var e = a.b.c.d; for(var i = 0; i < 1000; i++) e(i);
Reduce the number of dots (object.property) and brackets (object["property"]) in your program by caching frequently used objects and properties. Nested properties are the worst offenders (object.property.property.property).
Here is an example of minimizing lookups in a loop. Instead of this:
for (i=0; i<someArrayOrObject.length; i++)
Do this:
for (i=0, var n=someArrayOrObject.length; i<n; i++)
Also, accessing a named property or object requires a lookup. When possible, refer to the object or property directly by using an index into an object array. So instead of this:
var form = document.f2; // refer to form by name
Do this:
var form = document.forms[1]; // refer to form by position
Shorten Scope Chains
Every time a function executes, JavaScript creates an execution context that defines its own little world for local variables. Each execution context has an associated scope chain object that defines the object's place in the document's hierarchy. The scope chain lists the objects within the global namespace that are searched when evaluating an object or property. Each time a JavaScript program begins executing, certain built-in objects are created.
The global object lists the properties (global variables) and predefined values and functions (Math, parseInt(), etc.) that are available to all JavaScript programs.
Each time a function executes, a temporary call object is created. The function's arguments and variables are stored as properties of its call object. Local variables are properties of the call object.
Within each call object is the calling scope. Each set of brackets recursively defines a new child of that scope. When JavaScript looks up a variable (called variable name resolution), the JavaScript interpreter looks first in the local scope, then in its parent, then in the parent of that scope, and so on until it hits the global scope. In other words, JavaScript looks at the first item in the scope chain, and if it doesn't find the variable, it bubbles up the chain until it hits the global object.
That's why global scopes are slow. They are worst-case scenarios for object lookups.
During execution, only with statements and catch clauses affect the scope chain.
Avoid with Statements
The with statement extends the scope chain temporarily with a computed object, executes a statement with this longer scope chain, and then restores the original scope chain. This can save you typing time, but cost you execution time. Each additional child node you refer to means more work for the browser in scanning the global namespace of your document. So instead of this:
with (document.formname) { field1.value = "one"; field2.value = "two";... }
Do this:
var form = document.formname; form.field1.value = "one"; form.field2.value = "two;
Cache the object or property reference instead of using with, and use this variable for repeated references. with also has been deprecated, so it is best avoided.
Add Complex Subtrees Offline
When you are adding complex content to your page (like a table), you will find it is faster to build your DOM node and all its sub-nodes offline before adding it to the document. So instead of this (see Listing 10.1):
Listing 10.1 Adding Complex Subtrees Online
var tableEl, rowEl, cellEl; var numRows = 10; var numCells = 5; tableEl = document.createElement("TABLE"); tableEl = document.body.appendChild(tableEl); for (i = 0; i < numRows; i++) { rowEl = document.createElement("TR"); for (j = 0; j < numCells;j++) { cellEl = document.createElement("TD"); cellEl.appendChild(document.createTextNode("[row "+i+" cell "+j+ "]")); rowEl.appendChild(cellEl); } tableEl.appendChild(rowEl); }
Do this (see Listing 10.2):
Listing 10.2 Adding Complex Subtrees Offline
var tableEl, rowEl, cellEl; var numRows = 10; var numCells = 5; tableEl = document.createElement("TABLE"); for (i = 0; i < numRows; i++) { rowEl = document.createElement("TR"); for (j = 0; j < numCells;j++) { cellEl = document.createElement("TD"); cellEl.appendChild(document.createTextNode("[row " +i+ " cell "+j+"]")); rowEl.appendChild(cellEl); } tableEl.appendChild(rowEl); } document.body.appendChild(tableEl);
Listing 10.1 adds the table object to the page immediately after it is created and adds the rows afterward. This runs much slower because the browser must update the page display every time a new row is added. Listing 10.2 runs faster because it adds the resulting table object last, via document.body.appendChild().
Edit Subtrees Offline
In a similar fashion, when you are manipulating subtrees of a document, first remove the subtree, modify it, and then re-add it. DOM manipulation causes large parts of the tree to recalculate the display, slowing things down. Also, createElement() is slow compared to cloneNode(). When possible, create a template subtree, and then clone it to create others, only changing what is necessary. Let's combine these two optimizations into one example. So instead of this (see Listing 10.3):
Listing 10.3 Editing Subtrees Online
var ul = document.getElementById("myUL"); for (var i = 0; i < 200; i++) { ul.appendChild(document.createElement("LI")); }
Do this (see Listing 10.4):
Listing 10.4 Editing Subtrees Offline
var ul = document.getElementById("myUL"); var li = document.createElement("LI"); var parent = ul.parentNode; parent.removeChild(ul); for (var i = 0; i < 200; i++) { ul.appendChild(li.cloneNode(true)); } parent.appendChild(ul);
By editing your subtrees offline, you'll realize significant performance gains. The more complex the source document, the better the gain. Substituting cloneNode instead of createElement adds an extra boost.
Concatenate Long Strings
By the same token, avoid multiple document.writes in favor of one document.write of a concatenated string. So instead of this:
document.write(' string 1'); document.write(' string 2'); document.write(' string 3'); document.write(' string 4');
Do this:
var txt = ' string 1'+ ' string 2'+ ' string 3'+ ' string 4'; document.write(txt);
Access NodeLists Directly
NodeLists are lists of elements from object properties like .childNodes and methods like getElementsByTagName(). Because these objects are live (updated immediately when the underlying document changes), they are memory intensive and can take up many CPU cycles. If you need a NodeList for only a moment, it is faster to index directly into the list. Browsers are optimized to access node lists this way. So instead of this:
nl = document.getElementsByTagName("P"); for (var i = 0; i < nl.length; i++) { p = nl[i]; }
Do this:
for (var i = 0; (p = document.getElementsByTagName("P")[i]); i++)
In most cases, this is faster than caching the NodeList. In the second example, the browser doesn't need to create the node list object. It needs only to find the element at index i at that exact moment.
Use Object Literals
Object literals work like array literals by assigning entire complex data types to objects with just one command. So instead of this:
car = new Object(); car.make = "Honda"; car.model = "Civic"; car.transmission = "manual"; car.miles = 1000000; car.condition = "needs work";
Do this:
car = { make: "Honda", model: "Civic", transmission: "manual", miles: 1000000, condition: "needs work" }
This saves space and unnecessary DOM references.