I was playing around with DOM DocumentFragments recently, in JavaScript, seeing what I could make with them. Roughly speaking, a DocumentFragment is a lightweight container that can hold DOM nodes. It’s part of the DOM 1 specification and is supported in all modern browsers (it was added to Internet Explorer in version 6).
In reading up on them I came across an interesting point, from the specification:
Furthermore, various operations — such as inserting nodes as children of another Node — may take
DocumentFragment
objects as arguments; this results in all the child nodes of theDocumentFragment
being moved to the child list of this node.
This means that if you take a bunch of DOM nodes and append them to a fragment then you can simply append the fragment to the document, instead (and achieve the same result – as if you had appended each node individually). I instantly smelled a possible performance improvement here. I investigated a little bit further and noticed that DocumentFragments support the cloneNode
method, as well. This provides all the functionality that you need to highly-optimize your DOM insertion code.
I set up a simple demo to test the theory.
Let’s take the situation where you have a bunch of DOM nodes that you need to append into the document (in the demo it’s 12 nodes – 8 at the top level – against a whole mess of divs).
var elems = [ document.createElement("hr"), text( document.createElement("b"), "Links:" ), document.createTextNode(" "), text( document.createElement("a"), "Link A" ), document.createTextNode(" | "), text( document.createElement("a"), "Link B" ), document.createTextNode(" | "), text( document.createElement("a"), "Link C" ) ]; function text(node, txt){ node.appendChild( document.createTextNode(txt) ); return node; }
Normal Append
If we wanted to append these nodes into the document we would probably do it in this traditional manner: Looping through the nodes and cloning them individually (so that we can continue to append them all throughout the document).
var div = document.getElementsByTagName(“div”);
for ( var i = 0; i < div.length; i++ ) { for ( var e = 0; e < elems.length; e++ ) { div[i].appendChild( elems[e].cloneNode(true) ); } }[/js] DocumentFragment Append
However, when we bring DocumentFragments into the picture we can immediately see a different structure. To start we append all our nodes into the fragment itself (built using the createDocumentFragment
method).
But the interesting point comes when it’s time to actually insert the nodes into the document: We only have to call appendChild
and cloneNode
once for all the nodes!
var div = document.getElementsByTagName(“div”);
var fragment = document.createDocumentFragment();
for ( var e = 0; e < elems.length; e++ ) {
fragment.appendChild( elems[e] );
}
for ( var i = 0; i < div.length; i++ ) {
div[i].appendChild( fragment.cloneNode(true) );
}[/js]
Setting some time stamps we can see our results pay off in spades:
Browser | Normal (ms) | Fragment (ms) |
---|---|---|
Firefox 3.0.1 | 90 | 47 |
Safari 3.1.2 | 156 | 44 |
Opera 9.51 | 208 | 95 |
IE 6 | 401 | 140 |
IE 7 | 230 | 61 |
IE 8b1 | 120 | 40 |
As it turns out: A method that is largely ignored in modern web development can provide some serious (2-3x) performance improvements to your DOM manipulation.
Wayne (July 21, 2008 at 8:25 am)
Wow, well done. That’s a significant difference.
Oncle Tom (July 21, 2008 at 8:32 am)
Well at least this is a good tip to know. It’s fun to see there are actually still some parts of JavaScript/Dom we don’t know/use yet!
It’s nice to see that results are quite homogeneous with Fragment and all browsers. IE8 is damn quick too.
Thanks for your knowledge :-)
Vijay Santhanam (July 21, 2008 at 8:46 am)
Nice work John!
I had assumed the DOM 1 and 2 specs had been scraped of all it’s goodness, but I was clearly wrong!
Is jQuery only going to get faster?
John Resig (July 21, 2008 at 8:47 am)
@Vijay Santhanam: I have some very exciting DOM performance improvements coming in jQuery 1.3 (based upon my research here – and other points). We’re going to see some awesome jumps in speed.
Tane Piper (July 21, 2008 at 8:53 am)
I hope this makes it into the next release of jQuery, along with Brandon’s improvements to the offset() method. Another ~1000% speed increase? :)
Cloudream (July 21, 2008 at 9:00 am)
No js engine developer points this out before, don’t they code js?
nice research :D
Mathieu 'p01' Henri (July 21, 2008 at 9:02 am)
It is common practice to use DocumentFragment when developing web applications for devices and mobile phones. See [1,2]
Notice that since the DocumentFragment is not in the document itself, CSS are not applied to its elements. CSS are only applied once the DocumentFragment is injected into the document. This also explain a part of the performance gain, but means that you won’t be able to fiddle with the getComputedStyle( aChildOfTheDocumentFragment );
[1] http://dev.opera.com/articles/view/efficient-javascript/?page=3#modifyingtree
[2] http://dev.opera.com/articles/view/cross-device-development-techniques-for/#domfragments
Matt Murphy (July 21, 2008 at 9:12 am)
Nice stuff.
Jade Tuers (July 21, 2008 at 9:13 am)
John-David Dalton has done similar research for Prototype some months ago, but it hasn’t been implement to this day. The speed improvement is amazing.
http://dev.rubyonrails.org/ticket/11468#comment:5
Good to see you are taking this on, hope to see it in jQuery soon so other can follow.
Wade Harrell (July 21, 2008 at 9:30 am)
huh, i regret now not looking deeper into jquery source and assuming this is the way it has always worked, i certainly code it that way, i.e.
$(“#mynode”).append($(“”).each(function(tI,t){$(t).append …}
hm, i guess in that example the table becomes a container, but it is equal to a fragment? perhaps, as you did, a class based selector provides a better example.
an0n1 m0us (July 21, 2008 at 10:03 am)
Does this mean that whilst manually traversing the DOM in Firebug’s HTML tab – after changing the DOM by adding a node or whatever – elements will not appear in the document but above or below the html tag?
I ask this because it seems familiar to a strange behaviour I’ve seen using jQuery. For some reason divs appear above the HTML tag (a least in Firebug) when really they should be inserted into the document about 50% in.
I’m using the .before() method but have tried others without luck.
Matt (July 21, 2008 at 10:09 am)
You can also build up larger document fragments through doubling and end up with an optimum number of inserts overall.
Julien Lecomte (July 21, 2008 at 10:41 am)
More web performance tips (including using the DocumentFragment interface) can be found there:
http://www.slideshare.net/julien.lecomte/high-performance-ajax-applications
Cheers!
Julien
Scott Johnson (July 21, 2008 at 11:49 am)
The technique that I have used recently involved creating an element, such as a div, appending a bunch of nodes to that element, and then appending that element to the document. I’m thinking that this would perform similarly, but am I wrong? Is there any advantage to using a document fragment in place of a div for this operation?
John Resig (July 21, 2008 at 12:04 pm)
@Jade: It’s weird that in some of the results the speed decreased after introducing fragments. I wonder why.
@Wade: A fragment is different from a disconnected element. A fragment lets you manipulate multiple nodes as if they were one.
@an0n1 m0us: I’m not familiar with that bug – but it doesn’t sound related to the issue at hand.
@Scott: You don’t have to have extra divs littering you document – for one.
Scott Johnson (July 21, 2008 at 12:08 pm)
John, I’m not talking about an extra div. I’m talking about a div that is essential to the layout of the page. By your response, though, it sounds like the performance is going to be very similar with the two techniques. This is good to know, especially since I just sort of stumbled on this technique during a late night hacking session. Heck, if my JS is anywhere near as fast as yours, I’m impressed. ;)
ML (July 21, 2008 at 12:41 pm)
I wonder how that compares with generating HTML for all added elements and appending that via $(document.body).append(html).
Marius Gundersen (July 21, 2008 at 1:08 pm)
I’ve spent the last week working on a php project that uses the DOM, and I made my own class with custom functions to add multiple children. Oh well, away with that class, and in with DocumentFragment.
David Smith (July 21, 2008 at 1:13 pm)
We use this technique in Adium’s message view for inserting all content; specifically, we take an html file, process it in objective-c code, then use createContextualFragment to turn it into a document fragment and append it.
Marc (July 21, 2008 at 3:23 pm)
Would it be wise to empty the fragment after you are done with it? In other words, setting it to null or something?
MisterPerson (July 21, 2008 at 4:22 pm)
Genius.
Ash (July 21, 2008 at 5:34 pm)
Something to be aware of while working with DocumentFragment and CheckBox in IE6.
var fr=document.createDocumentFragment();
var ip=document.createElement(‘input’);
ip.type = ‘checkbox’;
fr.appendChild(ip);
ip.checked = true;
document.body.appendChild(fr);
In the above code the checkbox doesn’t get checked in IE6.
see the forum for more info
http://forums.microsoft.com/msdn/ShowPost.aspx?PostID=842144&SiteID=1&pageid=0
karl dubost, w3c (July 22, 2008 at 12:06 am)
I was curious to know if it was known or not. So I have looked at koders.
http://www.koders.com/default.aspx?s=createDocumentFragment&btn=&la=JavaScript&li=*
It gave me “4,175 occurences”.
sunnybear (July 22, 2008 at 4:14 am)
John, cached version of this example works even faster.
Compare with
var div = document.getElementsByTagName("div");
var dv = document.createElement("div");
var parent = div[0].parentNode;
for ( var e = 0; e
sunnybear (July 22, 2008 at 4:15 am)
Code example
var div = document.getElementsByTagName("div");
var dv = document.createElement("div");
var parent = div[0].parentNode;
for ( var e = 0; e < elems.length; e++ ) {
dv.appendChild( elems[e].cloneNode(true) );
}
for ( var i = 0; i < div.length; i++ ) {
// for IE
parent.replaceChild(dv.cloneNode(true),div[i]);
// for other
div[i] = dv.cloneNode(true);
}
Party Ark (July 22, 2008 at 5:11 am)
You’ve rather artificially made IE super-slow for your old-fashioned Normal Append by resolving div[i] for every iteration; this will double the speed making the difference between Normal and DocFrag rather less dramatic:
var currentDiv;
for ( var i = 0; i < div.length; i++ ) {
currentDiv = div[i];
for ( var e = 0; e
That makes it twice as quick. But docFrag is still a bit quicker.
Party Ark (July 22, 2008 at 5:13 am)
Sorry, try again:
var currentDiv;
for ( var i = 0; i < div.length; i++ ) {
currentDiv = div[i];
for ( var e = 0; e < elems.length; e++ ) {
currentDiv.appendChild( elems[e].cloneNode(true) );
}
}
Tino Zijdel (July 22, 2008 at 6:19 am)
Sorry John, but you’re comparing apples to pears and thus are coming to the wrong conclusion. Basically using createDocumentFragment isn’t faster than say replacing that line in your testcase by:
var fragment = document.createElement(‘span’);
In fact, this is equally fast in at least Opera 9.51 and IE8b1 and even twice as fast in Firefox 3.
It is /obvious/ that when you have to do more iterations (and indeed as Party Ark commented in a not-so-efficient way so that it hurts performance in IE extra) and do more appendChild()’s instead of using a ‘detached’ wrapper (any wrapper, be it a DocumentFragment or any actual element) that you can clone and append as a whole you will see a perceived degraded performance.
But it isn’t about performance; it’s about wether you don’t need/want the extra element wrapper, and only in that case DocumentFragment is the right container to use in your code so that you don’t have to do multiple appends on a non-detached element (which, I assume, everyone knowns is slower than working with detached elements).
Party Ark (July 22, 2008 at 6:52 am)
Tino – you say it’s /obvious/ that using more iterations slows things down, but if you didn’t know about documentFragment then you’d have no choice.
The difficulty I feel is that John has rather over-emphasised the performance benefits, and not really clarified that’s it’s only useful in circumstances where you absolutely must have an ‘unwrapped’ collection of HTML elements.
Furthermore, it’s only really of performance benefit if you have to clone your elements repeatedly, a combination of circumstances that probably doesn’t happen very much. Maybe the inner workings of JQuery can benefit.
But it’s certainly more elegant than nesting loops, and it’s always useful to be (re)minded of the more obscure corners of DOM manipulation.
Tino Zijdel (July 22, 2008 at 7:11 am)
No, John is mixing the usefullness of DocumentFragment with the more general performance trick of working with detached elements when creating large HTML fragments using DOM-methods.
If this blogpost is about performance than it should focus on the more general rule to work with detached elements and mention DocumentFragment as the container to use if you don’t want an actual wrapping element.
If this blogpost is about the benefits of DocumentFragement it should not focus on performance.
Now when reading this blogpost people might tend to think that they should always use DocumentFragment (even when they only need to insert one element, or a set of elements that is already wrapped into one) since ‘it is faster’, which is just untrue.
I do agree that reminding people of the existance of DocumentFragment is usefull, but it should not be presented as a magical performance increaser.
Daniel Steigerwald (July 22, 2008 at 9:09 am)
I have same experience with documentFragment vs. normal element performance, as a Tino. It’s all about working with detached el, so no DOM reflow/redraw. documentFragment is only tagless container, nothing more.
Brennan (July 22, 2008 at 2:28 pm)
I just used this to speed up a table sort I wrote in JavaScript. Thanks! Great tip.
Sean Sullivan (July 22, 2008 at 3:02 pm)
I investigated some of these caveats people mentioned above and came to the conclusion that John is spot on with his findings. Using DocumentFragment is definitely the speediest method.
http://www.ryboe.com/2008/07/22/increasing-appendchild-performance-with-dom-tricks.html
I used a benchmarking technique similar to what John-David Dalton used to build the case that Prototype should switch to the DocumentFragment method. Oddly enough, I feel that I’m seeing a much wider difference between FF2 and IE7 performance than you are witnessing here between FF3 and IE7.
John, would you be willing to share your method for benchmarking and performance testing?
Tino Zijdel (July 22, 2008 at 6:07 pm)
I noticed that the testresults are also enormously skewed by the fact that the iteration is being done on a live nodeList while doing DOM manipulations. The iteration method itself isn’t really a factor since the number of iterations is quite small, but converting the live nodeList to a static array already gives a huge performance boost (especially in IE) and showes even more clearly that createDocumentFragment isn’t really faster (or even slower) than using a real element as a container.
I put together a document that allows you to switch between various methods and measure execution times for each: http://therealcrisp.xs4all.nl/meuk/fragment.html
Ivan Lazarte (July 23, 2008 at 1:42 am)
Pretty sure Ext has been using these for a while, but I haven’t checked up on that lib in a year or so.
Joe McCann (July 23, 2008 at 10:09 am)
Nice work John. I’m assuming more of this type stuff will be in your upcoming book?
Alan (July 23, 2008 at 11:16 am)
This is not the same as working with a detached element. Tino’s example proves it.
Looking at the source when using a document fragment you see:
<div><hr><b>Links:</b> <a>Link A</a> | <a>Link B</a> | <a>Link C</a></div>
Using a detached span instead:
<div><span><hr><b>Links:</b> <a>Link A</a> | <a>Link B</a> | <a>Link C</a></span></div>
Note the extra spans. Passing a detached node as the argument to append child appends the node and it’s contents. Passing a document fragment only appends the contents.
Imagine updating an existing UL with new items. Using a detached node, say a span, you would have to iterate over its child nodes and append them one at a time or else you end up with a span inside the UL. Using a document fragment you can just append the fragment to the UL and all its children are automatically appended. No looping, no extra potentially invalid markup.
Tino Zijdel (July 23, 2008 at 12:01 pm)
“This is not the same as working with a detached element. Tino’s example proves it.”
I never said it was, but in my experience the usecase where you want to append several childnodes to an element that already has some childnodes is quite slim. Yet, it is a usecase and you should use DocumentFragment to accomodate that because it allows for simpler code, not because it is supposedly faster (you could also detach a node from the DOM, do your appending, en re-insert the node in the DOM).
André Luís (July 23, 2008 at 2:31 pm)
John,
Do you have any idea whether this approach (dom frag) is actually faster than just createElement-ing a node, filling it up with (huge) chuks of content via the ever-so-controversial innerHTML and _then_ appending the createElement-ed node to the DOM?
I saw this approach documented somewhere, can’t remember where… but I have no data to back it up in terms of performance. Just wondering…
Bill A. (July 29, 2008 at 11:57 am)
John,
Impressive as always. Although it’s somewhat dated, I remember coming across some work by Danny Goodman (JavaScript & DHTML Cookbook) that used a similar technique for dynamic table building. A benchmarking page can be found here: http://www.oreillynet.com/javascript/2003/05/06/examples/dyn_table_benchmarker_ora.html
Raziel (July 30, 2008 at 1:10 pm)
I started using the DocumentFragment about a year ago when looking for a mechanism to improve performance through what Tino describes as “detached elements” trick.
The way I used it was as a temporal container to put a section of markup that would suffer some DOM manipulation, and then re-appending it back to the main document. The difference I obtained between performing the DOM manipulation in the main document, and doing it within a document fragment ranged from 4 to 8 times faster with the DocumentFragment.
Whether this performance improvement could have been achieved with any other detached element, I don’t know -never had time to test (the DocumentFragment was really what I needed because of its characteristics). My appreciation was that DocumentFragment was actually faster though. My DOM manipulation does have a heavy use of jQuery 1.1, so I don;t know if that could mean something. For what I understand from Tino, it should not. Maybe John can provide more details.
These are the caveats I founf when working with it:
# A DocumentFragment behaves like a conventional Node with all of the same methods, except that a DocumentFragment does not have to be fully-formed XML and can be used to represent partly-formed or subsets of documents.
# Note it only contains a subset of a normal document’s interfaces. For example, getElementById does not exist. However since it’s a node, it contains the interfaces available in any Node.
# Internet Explorer does not follow the W3C DOM specification and creates a complete new document, thus you have available all Document’s functions.
# When a DocumentFragment is inserted into a Document (or indeed any other Node that may take children) the children of the DocumentFragment and not the DocumentFragment itself are inserted into the Node.
# Since the document fragment and its contents are not part of the main document, you cannot look for an element inside the fragment.
# After the insertion, the fragment is empty and cannot be reused unless you first add new children to it.
# Obvious, but sometimes overlooked: the fragment is self contained. This means, the fragment doesn’t have access to objects outside of it (i.e. part of the main document). Thus, even if you have CSS classes (class attribute) set in an element, it is not possible to query based on properties set and/or modified by that CSS class. Basically the classes set in the class attribute, if defined outside the fragment, become just simple labels.
# In Firefox (v.1.5 and 2), when the fragment is appended to the main document, any script part of the final fragment’s markup (for example, in the case of the Forms Designer any script contained in the template) will be executed. This may lead to scripts being executed twice. This behavious doesn’t apply to IE 6/7.
Juha Suni (July 30, 2008 at 5:16 pm)
Tino said:
“in my experience the usecase where you want to append several childnodes to an element that already has some childnodes is quite slim”
I quite often need to append rows to a table. I’d imagine it’s a fairly common scenario in many applications.
Tino Zijdel (July 30, 2008 at 7:54 pm)
“I quite often need to append rows to a table. I’d imagine it’s a fairly common scenario in many applications.”
And did you take into account the fact that tables have an implicit , or did you ever run into trouble there? ;)
documentFragment is a solution, just not in all cases and especially isn’t a performance ‘solution’
Tino Zijdel (July 30, 2008 at 7:56 pm)
<tbody> I meant – John: please fix your parser (and with it the default background-color)
Steve Whipple (August 7, 2008 at 1:57 pm)
Unless I missed something here, these do not seem surprising:
The first test goes through JS code div.length * elems.length times.
The second test goes through JS code div.length + elems.length times.
The rest of the work is done with the browser compiled code.
Did I mis-understand something?
jcarpent (August 11, 2008 at 12:23 pm)
Nicholas C.Zakas “JavaScript For Web Developers”, Wiley 2005
Pages 592,178 and 372: DocumentFragment uses.
“Nice work John!”, since 2005.
Parand (August 13, 2008 at 5:39 pm)
Semi-related question: I started using documentfragment for moving nodes around (sorting a table). Somewhere between putting a node into the documentfragment and putting it back into the doc its event handlers seem to get lost – no events fire. Anyone else seeing this, and any solutions?
Elijah Insua (August 14, 2008 at 1:09 am)
Excellent!
This will also aid in Selection/Range development! I’m currently using your example, but will convert as soon as there is support in jQuery :)
Andy (August 21, 2008 at 4:15 am)
Hi John, this is a silly exercise that you’re demonstrating. Although I suppose many people out there might be using the first ‘normal append’, it should be clear to all that using a quadratic algorithm should be slower than using a linear one.
(with N=div.length, the ‘normal append’ would result in N^2 node appends, whereas your ‘fragment append’ would result in N+1 node appends)
Nice to point out the usefulness of documentFragment.
Ruben (November 24, 2008 at 12:44 pm)
Hi John,
I’d like to know your opinion on this:
imagine you have an element on the DOM (any html element at all) witch you need to regulary manipulate (say change its width for example).
witch you think is better (in terms of performance)?
a) use document.getElementById(myElemId) everytime you need to change the width or
b) var myElemHandler = document.getElementById(myElemId) and everytime you need to modify myElem do this:
myElemHandler.style.width = new width
should myElemHandler be a DocumentFragment?
Thank you in advance!
Ruben
Anon (November 25, 2008 at 4:18 pm)
@Ruben:
I’m not John, but I would think that it is much faster to reference a variable, than calling the document.getElementById() method each time.
Ruben (November 26, 2008 at 4:53 am)
@Anon:
In that case, myElemHandler acts as some kind of a pointer to the html element right?
Anyway what is myElemHandler? if i make alert (myElemHandler) i get html object. But is it the object on the DOM or a clone of that object?
I know this may be a small detail, but if it is a clone i supose it allocates some memory too right?