Optimizing the views list creation with document fragment

Before using backbone when working with a list of html nodes I always made it my duty to append them only in one big chunk of text. Manipulating the dom is slow, it triggers a reflow of the page, this is costly even on small lists, specially on mobile.

So being not sure what to do with backbone I decided to append each view directly in my container like this.

  var jqGuestList = $("#guestList").empty();
 
    items.each(function(item){
        var view = new wedapp.view.GuestItem({
            model: item,
            collection: this.collection
        });
 
        jqGuestList.append(view.render().el);
    });

Of course this solution made me grind my teets, I did not want to append each view individually.

document.createDocumentFragment()

Fortunately, there is another solution than parsing the views as a string and then merging them together. You can append all the views to a document fragment and then append it to your list container. Since it is a document fragement it retains all the qualities of a DOM node (like finding node throughout it). On older browser like ie7, ie8 you will gain an average of 3x performance.

The cool thing about document fragments is that until it is added to the page it does not trigger a browser reflow, which is the really expensive thing here.

var jqGuestList = $("#guestList").empty();
 
var frag = document.createDocumentFragment();
items.each( function(item) {
    var view=new wedapp.view.GuestItem({model:item});
    frag.appendChild(view.render().el);
});
 
jqGuestList.append(frag);

Update

One commentator created a small test on jsperf to see the difference. Please remember that the test is only on 4 nodes, but it seems only legacy browser (ie8 and less) really benefit from this.

Still, I consider it a best practice and I can testify that on a hundred nodes, it make a significant difference on IE.

Update2:
The test provided did not took into account browser reflow. Trying to get this to be taken into, I kind of screwed up the test , crap. Anyway test with your own eyes, I personally saw a difference in loading and hang up time. Even on mobile with webkit.

Cedric Dugas is a front-end veteran with more than 9 years under the belt, between his open source projects & entrepreneurial ambitions he is a product architect at CakeMail. Why don't you follow him on twitter.

10 Comments on "Optimizing the views list creation with document fragment"

  1. Shane Jonas says:

    Interesting, Is this faster than Backbone.View's 'make' method? Also, I would keep track of the new views you're making so that you can clean them up afterward.

  2. Emanuel says:

    small annotation: instead of writing…

    var jqGuestList = $(“#guestList”);
    jqGuestList.empty();

    …you can just write…

    var jqGuestList = $(“#guestList”).empty();

    …as the $.fn.empty-method returns ‘this’.

    nice article btw :-)

  3. Cedric Dugas says:

    Using make would probably be about the same I think Shane,

    Thanks Emanuel, missed that!

  4. check_ca says:

    Note that the real speed optimization is to work on a node detached from the document. DocumentFragment is just a way of doing it.

    See this test for example:
    http://jsperf.com/createdocumentfragment-vs-appendchild-on-non-appended-e

  5. check_ca says:

    Just updated the jsperf test in order to be more accurate. It seems DocumentFragment is not faster than “detached node”.

    http://jsperf.com/createdocumentfragment-vs-appendchild-on-non-appended-e/2

  6. Cedric Dugas says:

    Your tests does not take ie8 and ie7 into account, and also im not sure it take into account mobile

    Update: now it does!

  7. Dave Ward says:

    Wouldn’t a fairer comparison be something like this?

    var jqGuestList = $("#guestList").detach();
    
    jqGuestList.empty();
    
    items.each(function(item){
        var view = new wedapp.view.GuestItem({
            model: item,
            collection: this.collection
        });
    
        jqGuestList.append(view.render().el);
    });
    
    jqGuestList.appendTo('#yourContainer');
  8. You do not need to try to force a reflow (e.g.: by accessing document.body.offsetHeight) to test this, though you can add it for kicks. With the exception of a small difference in ie7, my test shows exactly what your article originally set out to show: http://jsperf.com/document-fragment-vs-html-element-append

    @Dave Ward’s method also works; however, it can cause problems when the loop that builds the list is asynchronous (e.g.: adds only a chunk of elements, then breaks for something else) and the container is a necessary component of the layout. If a reflow/repaint happens while the container is detached, horrible ugly things can happen to your page. Also, this assumes that the outer container (#yourContainer) contains only the list container (#guestList) or that the list container is the last child of the outer container.

    A better method may be to clone the list container node (but not deeply so as to remove the child nodes), append to the cloned node, and then replace the original list container with the cloned container. Admittedly this will cause any events on the list container to be lost, but this can be solved easily with event delegation.

    Regardless, in the pursuit of Science!, someone should probably take the time to add these two alternate methods to the benchmark to see where they stand.

  9. I have put up a test page that simulates a slow connection so that we all can experience the pain together. To feel maximum pain, scroll down right after you load up the page. Experience reflow.

  10. Roger says:

    This breaks something interesting

    if you have
    el: ‘div’
    className: ‘foo’

    if you use documentFragment technique, every view loses the className they will all just be

    not sure why this is

Got something to say? Go for it!