andrew.hedges.name / blog

innerHTML versus the DOM: Can’t we all just get along?

19 April 2008 · Estimated reading time: 6 minutes

Having tested the relative speed of innerHTML versus DOM node replacement myself, I read with interest When innerHTML isn’t Fast Enough. I have put together a test of my previous 2 techniques plus one inspired by that article.

Each of the buttons below writes the phrase Chubby bunny 10,000 times to the container div, clears the content, then does it again. What I’m trying to test here is the effect of the different ways of clearing out the DOM nodes from the container. Code and discussion follows.

innerHTML

This technique is the simplest and, probably, the most common on the web today. The following code builds up a long string of HTML, dumps it into the innerHTML attribute of the container div, then sets the content back to an empty string. Rinse, and repeat.

// Start 1st pass
innerHTML  = [];
iterations = iters + 1;
while (—iterations > 0) {
    innerHTML[innerHTML.length] = htmlDiv;
}
resultDiv.innerHTML = innerHTML.join('');
resultDiv.innerHTML = '';

// Start 2nd pass innerHTML = []; iterations = iters + 1; while (—iterations > 0) { innerHTML[innerHTML.length] = htmlDiv; } resultDiv.innerHTML = innerHTML.join('');

DOM replace

The following code builds up a document fragment by appending div nodes to it. The fragment is then appended to the container, a new fragment is built up, then the nodes are swapped out in one step.

// Start 1st pass
container  = document.createDocumentFragment();
iterations = iters;
container.appendChild(nodeDiv);
while (—iterations > 0) {
    container.appendChild(container.firstChild.cloneNode(true));
}
resultDiv.appendChild(container);

// Start 2nd pass cloneDiv = resultDiv.cloneNode(false); cloneDiv.id = 'result-domreplace'; container = document.createDocumentFragment(); iterations = iters; container.appendChild(nodeDiv); while (—iterations > 0) { container.appendChild(container.firstChild.cloneNode(true)); } resultDiv.parentNode.replaceChild(cloneDiv, resultDiv); cloneDiv.appendChild(container);

innerHTML + DOM replace

The following code combines inserting a string of HTML via the innerHTML attribute with the one-step swapping goodness of the DOM replace code.

// Start 1st pass
innerHTML  = [];
iterations = iters + 1;
while (—iterations > 0) {
    innerHTML[innerHTML.length] = htmlDiv;
}
resultDiv.innerHTML = innerHTML.join('');

// Start 2nd pass container = resultDiv.cloneNode(false); innerHTML = []; iterations = iters + 1; while (—iterations > 0) { innerHTML[innerHTML.length] = htmlDiv; } container.innerHTML = innerHTML.join(''); resultDiv.parentNode.replaceChild(container, resultDiv);

View full source code

In theory, the last technique should be the fastest because the bottleneck with innerHTML is in removing the nodes by setting its value to an empty string. By building up a document fragment then swapping it out with the parentNode.replaceChild technique, we avoid using innerHTML to tear down the DOM.

In my testing, however, results were all over the shop. In Firefox, the results were as I expected: innerHTML was slowest, DOM replacement faster, and the combo technique fastest. In Internet Explorer, innerHTML was the fastest, the combo technique nearly as fast, and straight DOM manipulation nearly an order of magnitude slower. In Safari 3, I found no significant difference among the 3 techniques.

The following numbers are averages of 5 runs of each technique in the various browsers, expressed in milliseconds.


innerHTML DOM replacement innerHTML + DOM
Safari 3 109 104 96
Safari 2 489 849 457
Firefox 3b4 1588 1030 523
Firefox 2 1386 1019 401
IE 8b1 900 2131 844
IE 7 290 1275 475
IE 6 339 1880 373
Opera 9.27 266 847 675

From the above, it looks like the biggest advantage of the combo technique is its consistency across browsers. While straight innerHTML is faster on some browsers and straight DOM manipulation is faster on others, combining the 2 techniques yields decent performance across browsers.

Update: I added numbers for Safari 2, IE 8b1 and Opera 9.27 to the list above. My conclusion still appears to hold.