Working with the DOM is one of the most common tasks of client-side JavaScript. This is also the main source of problems (and the notoriety of JavaScript), because DOM methods are implemented differently in different browsers. That is why using a good JavaScript library that hides differences between browsers can significantly speed up development.
We will consider some of the patterns recommended for accessing and modifying the DOM, which focus on high performance.
Accessing the DOM tree
Accessing DOM tree is quite expensive operation; this is the bottleneck of JavaScript in terms of performance.
This is due to the fact that the DOM tree is usually implemented separately from the JavaScript engine. From the point of view of the browser, this makes some sense, since a JavaScript application may not need to access the DOM tree at all. And besides, other programming languages other than JavaScript can be used to work with the DOM tree (for example, VBScript in IE).
Therefore, you must strive to minimize the operations of accessing the DOM tree. It means that:
- Calls to DOM elements inside loops should be avoided
- It is advisable to assign references to DOM elements to local variables and work with these variables
- Use selector interface where possible
- The value of the length property should be stored in a local variable when iterating through HTML collections
Take a look at the following example, where the second cycle, despite the larger size, is tens, or even hundreds of times faster than the first, depending on the browser:
// anti-pattern
for (var i = 0; i < 100; i + = 1) {
document.getElementById("content").innerHTML+=i + ",";
}
// preferable - changes the value of a local variable
var i, content = "";
for (i = 0; i <100; i + = 1) {
content + = i + ",";
}
document.getElementById("output").innerHTML+=content;
In the following snippet, the second example (using a local variable) is preferable, although it contains one more line of program code and uses an additional variable:
// anti-pattern
var padding = document.getElementById("content").style.padding,
margin = document.getElementById("content").style.margin;
// preferred
var style = document.getElementById("content").style,
padding = style.padding,
margin = style.margin;
Using the selectors interface means the use of methods:
document.querySelector("span .selected");
document.querySelectorAll("#container div.first");
These methods take CSS selectors and return lists of DOM nodes matching the specified selection criteria. Selector methods are available in modern browsers and always perform faster than any other implementation of node selection based on the use of other DOM methods.
The latest versions of popular JavaScript libraries take advantage of the selector interface, so be sure to use the latest version of your favorite library.
In addition, add the id = ""
attribute to the elements that you have to access most often, because this will provide the ability to easily and quickly find them using the document.getElementById(myid)
method.
DOM tree manipulation
In addition to accessing DOM elements, you will often need to modify them, delete, or add new elements. Making changes to the DOM tree can cause the browser to redraw the page, as well as redistribute the elements (recalculate their coordinates and sizes), which can be a very expensive operation.
Here again, the basic principle is to try to minimize the number of operations that modify the DOM tree, which means to accumulate changes by executing them outside the "live" tree of the DOM document.
If you need to create a relatively large branch of a tree, you should try not to add it to the tree until it is completely ready. For this purpose, you can create a document fragment containing the nodes you need.
The following example shows how nodes should not be added:
// anti-pattern
// nodes are added to the tree as they are created
var p, t;
p = document.createElement("p");
t = document.createTextNode("example text");
p.appendChild(t);
document.body.appendChild(p);
p = document.createElement("p");
t = document.createTextNode("another example text");
p.appendChild(t);
document.body.appendChild(p);
It is much more preferable to create a fragment of the document, change it in "offline mode" and add it to the DOM tree when it is completely ready.
When it is added to the DOM tree, the contents of the fragment will be added, not the fragment itself. And it is really very convenient. So, a document fragment is a great way to wrap a lot of nodes, even when they are not inside the common parent element.
The following is an example of using a fragment of a document:
var p, t, fragmnt;
fragmnt = document.createDocumentFragment();
p = document.createElement("p");
t = document.createTextNode("some text");
p.appendChild(t);
fragmnt.appendChild(p);
p = document.createElement("p");
t = document.createTextNode("another text");
p.appendChild(t);
fragmnt.appendChild(p);
document.body.appendChild(fragmnt);
In this example, the document is changed only once, causing a single page redraw operation, while the previous anti-template example causes the page to be redrawn as each paragraph is added.
A document fragment is convenient to use when adding new nodes to the tree. However, when changing existing tree nodes, you should also strive to minimize the number of changes. To do this, you can create a copy of the required tree branch, make the necessary changes to the copy and, when it is ready, replace the original with it:
var oldnode = document.getElementById("content"),
clone = oldnode.cloneNode(true);
// perform operations on the copy ...
// when the copy is ready:
oldnode.parentNode.replaceChild(clone, oldnode);