Posted on May 5, 2014 by Chris Harrington

Using document fragments to append html to the DOM with JavaScript

The Problem

I’d recently been asked to solve some performance problems with a page that had previously been built. The page is responsible for allowing the user to choose an airport. It has some other fancy stuff going on, like favouriting airports and such, but the bottom line was that it was simply a list of airports, one of which the user would select. We saw some unusually high rendering times for the page and when I dug into the code, I saw that the page was being generated dynamically by using JavaScript to iterate over a JSON list of airports and building the view from a template. That list of airports was retrieved asynchronously, which is why the page is built using JavaScript instead of being rendered on the server. There’s a couple of ways to build pages in JavaScript with jQuery.

The First Solution

Here’s the idea behind the code that was in place before I made any changes. It’s slow, but not super slow. The performance problems mainly show up on older mobile devices.

var airports = [...], container = $("...");
for (var i = 0; i < airports.length; i++) {
    var view = _buildViewFromAirport(airports[i]);
    container.append(view);
}

The problem here is that we're updating the DOM for every airport in the list. If our list of airports is sufficiently small, that's not a huge problem, but plan for the worst, right? In my case, I was dealing with ~160 airports, which was enough to cause some older phones to chug.

The Second Solution

So how about we create an empty div, append all of the airport data to that div, then add that div to the DOM? This is better. Here's the code.

var airports = [...], container = $("..."), wrapper = $("<div />");
for (var i = 0; i < airports.length; i++) {
    var view = _buildViewFromAirport(airports[i]);
    wrapper.append(view);
}
container.append(wrapper);

In this case, we avoid the problem of hitting the DOM many times and append the wrapper to the DOM after the loop. This is obviously better than adding HTML to the DOM inside the loop. There's a yet faster way of doing this, however.

The Better Solution

This solution introduces the idea of document fragments. A document fragment is basically an empty div as in the second solution, but it's a special construct that's built via the document object and is used specifically for this purpose. The document fragment can basically be thought of as a div that has no footprint on the DOM or HTML when it's added. Here's the code:

var airports = [...], container = $("..."), fragment = document.createDocumentFragment();
for (var i = 0; i < airports.length; i++) {
    var view = _buildViewFromAirport(airports[i]);
    fragment.appendChild(view[0]);
}
container.append(fragment);

The code is roughly the same as in the second solution, but we're creating a document fragment to be used as the wrapper. Note that it's not a jQuery object, so we fall back to using native JavaScript methods to put it all together. This obviously avoids the problem of updating the DOM multiple times through the loop, but also makes use of a construct that was specifically designed for this situation.

The Proof

I've put some tests together on JSPerf that illustrate the vast differences between using document fragments and not. Click here to take a look. Bottom line? Document fragments are 436% faster than appending to the DOM in the middle of the loop (first solution), and 409% faster than using the jQuery div (second solution) in Chrome 34. So make sure that in the event you need to append to the DOM multiple times, use a document fragment!