I posted recently about a drag and drop plugin that I had created in the name of performance, but this was not the only performance bottleneck that I had to overcome in my application. As I said before, my application has two lists, one of which depends on the selection of the other for its content. Whenever the selection of the first list changes, the content of the other list updates, often with several hundred items. As I said before, one of the performance issues that I had, was the time it took to bind event listeners to all of those DOM elements for drag and drop. I solved this though event delegation. This change dramatically increased performance, but additional optimizations were still needed. Unfortunately, the issues were mostly beyond my control since the DOM was the source of the problem. Here is my original code.

$("#first-list li").live("mousedown", function() {
    $("#second-list").empty();

    $.each(content, function(i) {
        $("<li>").text(this.name).appendTo("#second-list");
    });
});

As this code shows, every time a user clicks on a list item in the first list, all of the items in the second list were removed using the jQuery.empty() function, and all of the items in the content array are appended to the second list.

So why is this slow?

This looks like pretty normal jQuery code and it works great until you have a lot of elements to append all at once. The reason that appending elements is slow, is because the browser must reflow the entire page every time an element is appended. Reflowing is the process that the browser’s layout manager goes through every time something changes size, or is added or removed from the page. During the process, the browser must recalculate the position and size of all of the elements on the page and then repaint it. Since reflowing occurs after appending each individual item to the DOM, performance can really take a hit.

The solution

The solution to this problem is to force the browser to reflow only once, and an unfortunate side effect is less simple looking code.

$("#first-list li").live("mousedown", function(e) {
    var html = [],
        len = content.length,
        i = 0;

    for(var j=0; j<len; j++) {
        html[i++] = "<li>";
        html[i++] = content[j].name;
        html[i++] = "</li>";
    }

    $("#second-list").html("<div>" + html.join('') + "</div>");
});

In this code, instead of using jQuery to create and append elements to the DOM, we actually create a long string of all of the elements that we want to append in memory, and then set it as the innerHTML of the second list. I have further optimized the code so that it actually creates an array of strings and then joins them, which is faster than doing string concatenation in most browsers. I have also wrapped all of the list items in a single DIV, which speeds up the browser because it can insert the elements as a block instead of individually.

So how does this perform?

Appending elements all at once performs much better than using jQuery to append the elements as they are being created, which would be expected. In my informal tests appending 500 elements, the grouped performance is often double the individual speed.

Conclusion

Performance is a very big issue if you are writing large scale applications, and it is unfortunate that the browser platform is slow in such a key area. That is why hacks like this exist: to artificially make things faster. It is my hope that browser makers will step up to the plate and make one of the most important parts of the browser faster for programmers, and most importantly for users. Until then however, we as developers need to get around these performance issues so that the experience that users have with web applications is a positive one. Performance is one of the final hurdles of browser based apps which I firmly believe are the future of computing.


About these ads