Increasing appendChild Performance with DOM Tricks
A recent blog post by John Resig has outlined a performance sensitive method of appending many elements to your document. His suggestion, using DocumentFragment as the container object to which to append all child nodes, is backed by some pretty impressive benchmarks. The most drastic performance improvements by using this method of appending objects to the document are witnessed by Internet Explorer and Safari, IE6 improved by 65%, IE7 by 73% and Safari by 71%.
In looking at his code used to test this, some users argued that his test cases were slightly skewed as the container object was handled in two different manners. In the case using appendChild for each childNode, the container object was looked up on the gathered div array using the loops index to point to the value. Why not store it they asked? This led to accusations that in the case that switches to using the DocumentFragment, the benefit may be derived from the idea that the container object is accessed as a stored value inside the loop. This also lead to questioning that, in his second case, the majority of the content was being appended to an object not yet living in the DOM.
This begs the questions– Does the perceived performance increase being witnessed in this case come from the fact that the container object is detached from the DOM while being appended? I decided to dive in…
The Procedure
To do this, I set up three cases and tested in both Firefox 2 and Internet Explorer 7. Knowing that the original idea, looping through the divs and accessing the container as div[i], was the least desirable, I decided to toss that out the window from the get go.
For my first case, "Appending to Attached Div", I stuck with the structure of John's control case, in which the nodes are appended directly to an element still active in the DOM. The one tweak I did make, was to store a reference to the container div rather than using div[i]. This was as simple as setting var container = div[i] inside the first loop. This resulted in:
var div = document.getElementsByTagName("div");
for ( var i = 0; i < div.length; i++ ) {
var container = div[i];
for ( var e = 0; e < elems.length; e++ ) {
container.appendChild( elems[e].cloneNode(true) );
}
}
Case 2 involved using John's DocumentFragment method verbatim. This presented an element of control to my test in relation to his.
In the 3rd case, I decided to use the method just like in case 2, however rather than using createDocumentFragment() I created a table element with createElement("table"). This was stored as the container to which to append the children elements.
var div = document.getElementsByTagName("div");
var container = document.createElement("table");
for ( var e = 0; e < elems.length; e++ ) {
container.appendChild( elems[e] );
}
for ( var i = 0; i < div.length; i++ ) {
div[i].appendChild( container.cloneNode(true) );
}
My Results
The results are as follow, with time to execute represented (in seconds).
| Appending to Attached Div | Appending to Document Fragment | Appending to Table | |
| FF2 | 2.046 | .430 | .481 |
| IE7 | 4.134 | 3.256 | 3.715 |
In Conclusion
After going through this test, I decided that John was definitely onto something. He found the most optimized way to approach this method. Between the two cases in which I appended the content to an object that was not yet in the DOM (for the elems array), the DocumentFragment method proved to be more efficient by about 10%. The one thing I would like to add–that I feel my test proved–is that when you are doing the majority of your appending, you should be doing it to an object that is not currently contained within the DOM. You can then later append that object to its destination container.
P.S. On a humorous note, the original case that John was using in his test took far too long for me to run with the sheer volume of div's I was trying to populate. In fact, it caused Firefox to prompt me to stop the script and Internet Explorer just locked up… Definitely make sure you use one of these presented solutions for appending nodes to improve your performance!



July 22nd, 2008 at 5:58 pm
TABLE seems to me to be an odd element to use as a container, especially because it will force the renderengine to create implicit TBODY elements, has special layout algorithms and isn't a valid container for the elements that are being put in there in the first place.
After some more testing I found that the iteration of the live nodeList while doing DOM manupilations is also of a critical factor. When transforming the live nodeList to a static array before iteration I get even better results and by thus eliminating another irrelevant factor from what we're trying to test the results at least on my PC still slightly favour using a real element as a container.
Here's my testdocument: http://therealcrisp.xs4all.nl/meuk/fragment.html
July 22nd, 2008 at 6:22 pm
Tino,
Great test doc. I like how you laid that out, makes it easy to quickly view the differences. I agree with you about using TABLE as the container, in hindsight that was kind of a lame choice.
Interesting observation about the live node array that we are appending to. When I run your test doc, I appear to be getting the best performance out of Case #6, "forwards traversing using a reference."
Your cases that use the static array actually appear to be a little bit slower on both of my machines, I wonder if this has to do with the fact that we have to loop through the divs to add them to the static array, then loop through again when appending. The effect appears to be that you are essentially widening the "inner loop" of the process.
The trade off of creating a static loop from the live nodes versus just appending to the nodes appears to be in favor of just using the live nodes when appending the "fragments"
July 22nd, 2008 at 6:49 pm
The conversion to a static array makes a huge difference at least in IE6 and IE7. I get slightly better results with the static array in Firefox 2 as well despite the fact that I need to iterate throught the divs twice.
Another advantage of using the static array iso the live NodeList is that you can also without problems use a DIV as a container (imagine what happens when you do that while iterating through the live NodeList of divs ;))
So I'm not sure how we can explain the other differences. Machine architecture and operating system and those sort of things in my experience have also always some influence on these kind of benchmarks…