18 min read

This article, by Thodoris Greasidis, the author of jQuery Design Patterns, presents some optimization techniques that can be used to improve the performance of jQuery applications, especially when they become large and complex.

We will start with simple practices to write performant JavaScript code, and learn how to write efficient CSS selectors in order to improve the page’s rendering speed and DOM traversals using jQuery. We will continue with jQuery-specific practices, such as caching of jQuery Composite Collection Objects, how to minimize DOM manipulations, and how to use the Delegate Event Observer pattern as a good example of the Flyweight pattern.

(For more resources related to this topic, see here.)

Optimizing the common JavaScript code

In this section, we will analyze some performance tips that are not jQuery-specific and can be applied to most JavaScript implementations.

Writing better for loops

When iterating over the items of an array or an array-like collection with a for loop, a simple way to improve the performance of the iteration is to avoid accessing the length property on every loop. This can easily be done by storing the iteration length in a separate variable, which is declared just before the loop or even along with it, as shown in the following code:

for (var i = 0, len = myArray.length; i < len; i++) {

    var item = myArray[i];

    /*...*/

}

Moreover, if we need to iterate over the items of an array that does not contain “falsy” values, we can use an even better pattern, which is commonly applied when iterating over arrays that contain objects:

var objects = [{ }, { }, { }];

for (var i = 0, item; item = objects[i]; i++) {

    console.log(item);

}

In this case, instead of relying on the length property of the array, we are exploiting the fact that access to an out-of-bounds position of the array returns undefined, which is “falsy” and stops the iteration. Another sample case in which this trick can be used is when iterating over Node Lists or jQuery Composite Collection Objects, as shown in the following code:

var anchors = $('a'); // or document.getElementsByTagName('a');

for (var i = 0, anchor; anchor = anchors[i]; i++) {

    console.log(anchor.href);

}

For more information on the “truthy” and “falsy” JavaScript values, you can visit https://developer.mozilla.org/en-US/docs/Glossary/Truthy and https://developer.mozilla.org/en-US/docs/Glossary/Falsy.

Using performant CSS selectors

Even though Sizzle (jQuery’s selector engine) hides the complexity of DOM traversals that are based on a complex CSS selector, we should have an idea of how our selectors perform. By understanding how CSS selectors can be matched against the elements of the DOM can help us write more efficient selectors, which will perform in a better way when used with jQuery.

The key characteristic of efficient CSS selectors is specificity. According to this, IDs and Class selectors will always be more efficient than selectors with many results, such as div and *. When writing complex CSS selectors, keep in mind that they are evaluated from right to left, and a selector gets rejected after recursively testing it against every parent element until the root of the DOM is reached.

As a result, try to be as specific as possible with the right-most selector in order to cut down the matched elements as early as possible during the execution of the selector:

// initially matches all the anchors of the page

// and then removes those that are not children of the container

$('.container a');

 

// performs better, since it matches fewer elements

// in the first step of the selector's evaluation

$('.container .mySpecialLinks');

Another performance tip is to use the Child Selector (parent > child) wherever applicable in an effort to eliminate the recursion over all the hierarchies of the DOM tree. A great example of this can be applied in cases where the target elements can be found at a specific descendant level of a common ancestor element:

// initially matches all the div's of the page, which is bad

$('.container div') ;

 

// a lot better better, since it avoids the recursion

// until the root of the DOM tree

$('.container > div');

 

// best of all, but can't be used always

$('.container > .specialDivs');

The same tips can also be applied to CSS selectors that are used to style pages. Even though browsers have been trying to optimize any given CSS selector, the tips mentioned earlier can greatly reduce the time that is required to render a web page.

For more information on jQuery CSS selector performance, you can visit http://learn.jquery.com/performance/optimize-selectors/.

Writing efficient jQuery code

Let’s now proceed and analyze the most important jQuery-specific performance tips. For more information on the most up-to-date performance tips about jQuery, you can go to the respective page of jQuery’s Learning Center at http://learn.jquery.com/performance.

Minimizing DOM traversals

Since jQuery has made DOM traversals such simple tasks, a big number of web developers have started to overuse the $() function everywhere, even in subsequent lines of code, making their implementations slower by executing unnecessary code. One of the main reasons that the complexity of the operation is so often overlooked is the elegant and minimalistic syntax that jQuery uses. Despite the fact that JavaScript browser engines have already become more faster, with performance comparable with many compiled languages, the DOM API is still one of their slowest parts, and as a result, developers have to minimize their interactions with it.

Caching jQuery objects

Storing the result of the $() function to a local variable, and subsequently, using it to operate on the retrieved elements is the simplest way to eliminate unnecessary executions of the same DOM traversals:

var $element = $('.Header');

if ($element.css('position') === 'static') {

    $element.css({ position: 'relative' });

}

$element.height('40px');

$element.wrapInner('<b>');

It is also highly suggested that you store Composite Collection Objects of important page elements as properties of our modules and reuse them everywhere in our application:

window.myApp = window.myApp || {};

myApp.$container = null;

myApp.init = function() {

    myApp.$container = $('.myAppContainer');    

};

$(document).ready(myApp.init);

Caching retrieved elements on modules is a very good practice when the elements are not going to be removed from the page. Keep in mind that when dealing with elements with shorter lifespans, in order to avoid memory leaks, you either need to ensure that you clear their references when they are removed from the page, or have a fresh reference retrieved when required and cache it only inside your functions.

Scoping element traversals

Instead of writing complex CSS selectors for your traversals, as follows:

$('.myAppContainer .myAppSection');

You can instead have the same result in a more efficient way using an already retried ancestor element to scope the DOM traversal. This way, you are not only using simpler CSS selectors that are faster to match against page elements, but you are also reducing the number of elements that need to be checked. Moreover, the resulting implementations have less code repetitions (they are DRYer), and the CSS selectors used are simple and more readable:

var $container = $('.myAppContainer');

$container.find('.myAppSection');

Additionally, this practice works even better with module-wide cached elements:

var $sections = myApp.$container.find('.myAppSection');

Chaining jQuery methods

One of the characteristics of all jQuery APIs is that they are fluent interface implementations that enable you to chain several method invocations on a single Composite Collection Object:

$('.Content').html('')

    .append('<a href="#">')

    .height('40px')

    .wrapInner('<b>');

Chaining allows us to reduce the number of used variables and leads us to more readable implementations with less code repetitions.

Don’t overdo it

Keep in mind that jQuery also provides the $.fn.end() method (http://api.jquery.com/end/) as a way to move back from a chained traversal:

$('.box')

    .filter(':even')

    .find('.Header')

    .css('background-color', '#0F0')

    .end()

    .end() // undo the filter and find traversals

    .filter(':odd') // applied on  the initial .box results

    .find('.Header')

    .css('background-color', '#F00');

Even though this is a handy method for many cases, you should avoid overusing it, since it can affect the readability of your code. In many cases, using cached element collections instead of $.fn.end() can result in faster and more readable implementations.

Improving DOM manipulations

Extensive use of the DOM API is one of the most common things that makes an application slower, especially when it is used to manipulate the state of the DOM tree. In this section, we will showcase some tips that can reduce the performance hit when manipulating the DOM tree.

Creating DOM elements

The most efficient way to create DOM elements is to construct an HTML string and append it to the DOM tree using the $.fn.html() method. Additionally, since this can be too restrictive for some use cases, you can also use the $.fn.append() and $.fn.prepend() methods, which are slightly slower but can be better matches for your implementation. Ideally, if multiple elements need to be created, you should try to minimize the invocation of these methods by creating an HTML string that defines all the elements and then inserting it into the DOM tree, as follows:

var finalHtml = '';

for (var i = 0, len = questions.length; i < len; i++) {

    var question = questions[i];

    finalHtml += '<div><label><span>' + question.title + ':</span>' +

'<input type="checkbox" name="' + question.name + '" />' +

'</label></div>';

}

$('form').html(finalHtml);

Another way to achieve the same result is using an array to store the HTML for each intermediate element and then joining them right before inserting them into the DOM tree:

var parts = [];

for (var i = 0, len = questions.length; i < len; i++) {

    var question = questions[i];

    parts.push('<div><label><span>' + question.title + ':</span>' +

    '<input type="checkbox" name="' + question.name + '" />' +

    '</label></div>');

}

$('form').html(parts.join(''));

This is a commonly used pattern, since until recently it was performing better than concatenating the intermediate results with “+=”.

Styling and animating

Whenever possible, try using CSS classes for your styling manipulations by utilizing the $.fn.addClass() and $.fn.removeClass() methods instead of manually manipulating the style of elements with the $.fn.css() method. This is especially beneficial when you need to style a big number of elements, since this is the main use case of CSS classes and the browsers have already spent years optimizing it.

As an extra optimization step used to minimize the number of manipulated elements, you can try to apply CSS classes on a single common ancestor element, and use a descendant CSS selector to apply your styling ( https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_selectors).

When you still need to use the $.fn.css() method; for example, when your implementation needs to be imperative, we prefer using the invocation overload that accepts object parameters: http://api.jquery.com/css/#css-properties. This way, when applying multiple styles on elements, the required method invocations are minimized, and your code also gets better organized.

Moreover, we need to avoid mixing methods that manipulate the DOM with methods that are read from the DOM, since this will force a reflow of the page, so that the browser can calculate the new positions of the page elements.

Instead of doing something like this:

$('h1').css('padding-left', '2%');

$('h1').css('padding-right', '2%');

$('h1').append('<b>!!</b>');

var h1OuterWidth = $('h1').outerWidth();

 

$('h1').css('margin-top', '5%');

$('body').prepend('<b>--!!--</b>');

var h1Offset = $('h1').offset();

We will prefer grouping the nonconflicting manipulations together like this:

$('h1').css({

    'padding-left': '2%',

    'padding-right': '2%',

    'margin-top': '5%'

}).append('<b>!!</b>');

$('body').prepend('<b>--!!--</b>');

 

var h1OuterWidth = $('h1').outerWidth();

var h1Offset = $('h1').offset();

This way, the browser can try to skip some rerenderings of the page, resulting in less pauses of the execution of your code.

For more information on reflows, you can refer to https://developers.google.com/speed/articles/reflow.

Lastly, note that all jQuery generated animations in v1.x and v2.x are implemented using the setTimeout() function. This is going to change in v3.x of jQuery, which is designed to use the requestAnimationFrame() function, which is a better match to create imperative animations. Until then, you can use the jQuery-requestAnimationFrame plugin (https://github.com/gnarf/jquery-requestAnimationFrame), which monkey-patches jQuery to use the requestAnimationFrame() function for its animations when it is available.

Manipulating detached elements

Another way to avoid unnecessary repaints of the page while manipulating DOM elements is to detach the element from the page and reattach it after completing your manipulations. Working with a detached in-memory element is more faster and does not cause reflows of the page.

In order to achieve this, we will use the $.fn.detach() method, which in contrast with $.fn.remove() preserves all event handlers and jQuery data on the detached element:

var $h1 = $('#pageHeader');

var $h1Cont = $h1.parent();

$h1.detach();

 

$h1.css({

    'padding-left': '2%',

    'padding-right': '2%',

    'margin-top': '5%'

}).append('<b>!!</b>');

 

$h1Cont.append($h1);

Additionally, in order to be able to place the manipulated element back to its original position, we can create and insert a hidden “placeholder” element into the DOM. This empty and hidden element does not affect the rendering of the page and is removed right after the original item is placed back into its original position:

var $h1PlaceHolder = $('<div style="display: none;"></div>');

var $h1 = $('#pageHeader');

$h1PlaceHolder.insertAfter($h1);

 

$h1.detach();

 

$h1.css({

    'padding-left': '2%',

    'padding-right': '2%',

    'margin-top': '5%'

}).append('<b>!!</b>');

 

$h1.insertAfter($h1PlaceHolder);

$h1PlaceHolder.remove();

$h1PlaceHolder = null;

For more information on the $.fn.detach() method, you can visit its documentation page at http://api.jquery.com/detach/.

Using the Flyweight pattern

According to computer science, a Flyweight is an object that is used to reduce the memory consumption of an implementation that provides the functionality and/or data that will be shared with other object instances. The prototypes of JavaScript constructor functions can be characterized as Flyweights to some degree since every object instance can use all the methods and properties that are defined on its Prototype, until it overwrites them. On the other hand, classical Flyweights are separate objects from the object family that they are used with and often hold the shared data and functionality in special data structures.

Using Delegated Event Observers

A great example of Flyweights in jQuery applications are Delegated Event Observers that can greatly reduce the memory requirements of an implementation by working as a centralized event handler for a large group of elements. This way, we can avoid the cost of setting up separate observers and event handlers for every element and utilize the browser’s event bubbling mechanism to observe them on a single common ancestor element and filter their origin. Moreover, this pattern can also simplify our implementation when we need to deal with dynamically constructed elements since it removes the need of attaching extra event handlers for each created element.

For example, the following code attaches a single observer on the common ancestor element of several <button> elements. Whenever a click happens on one of the <button> elements, the event will bubble up to the parent element with the buttonsContainer CSS class, and the attached handler will be fired. Even if we add extra buttons later to that container, clicking on them will still fire the original handler:

$('.buttonsContainer').on('click', 'button', function() {

    var $button = $(this);

    alert($button.text());

});

The actual Flyweight object is the event handler along with the callback that is attached to the ancestor element.

Using $.noop()

The jQuery library offers the $.noop() method, which is actually an empty function that can be shared with implementations. Using empty functions as default callback values can simplify and increase the readability of an implementation by reducing the number of the required if statements. Such a thing can be greatly beneficial for jQuery plugins that encapsulate the complex functionality:

function doLater(callbackFn) {

    setTimeout(function() {

        if (callbackFn) {

            callbackFn();

        }

    }, 500);

}

 

// with $.noop()

function doLater(callbackFn) {

    callbackFn = callbackFn || $.noop();

    setTimeout(function() {

        callbackFn();

    }, 500);

}

In such situations, where the implementation requirements or the personal taste of the developer leads to using empty functions, the $.noop() method can be beneficial to lower the memory consumption by sharing a single empty function instance among all the different parts of an implementation. As an added benefit of using the $.noop() method for every part of an implementation, we can also check whether a passed function reference is actually the empty function by simply checking whether callbackFn is equal to $.noop().

For more information, you can visit its documentation page at http://api.jquery.com/jQuery.noop/.

Using the $.single plugin

Another simple example of the Flyweight pattern in a jQuery application is the jQuery.single plugin, as described by James Padolsey in his article 76 bytes for faster jQuery, which tries to eliminate the creation of new jQuery objects in cases where we need to apply jQuery methods on a single page element. The implementation is quite small and creates a single jQuery Composite Collection Object that is returned on every invocation of the jQuery.single() method, containing the page element that was used as an argument:

jQuery.single = (function(){

    var collection = jQuery([1]); // Fill with 1 item, to make sure length === 1

    return function(element) {

        collection[0] = element; // Give collection the element:

        return collection; // Return the collection:

    };

}());

The jQuery.single plugin can be quite useful when used in observers, such as $.fn.on() and iterations with methods, such as $.each():

$buttonsContainer.on('click', '.button', function() {

    // var $button = $(this);

    var $button = $.single(this); // this is not creating any new object

    alert($button);

});

The advantages of using the jQuery.single plugin originate from the fact that we are creating less objects, and as a result, the browser’s Garbage Collector will also have less work to do when freeing the memory of short-lived objects.

Keep in mind that the side effects of having a single jQuery object returned by every invocation of the $.single() method, and the fact that the last invocation argument will be stored until the next invocation of the method:

var buttons = document.getElementsByTagName('button');

var $btn0 = $.single(buttons[0]);

var $btn1 = $.single(buttons[1]);

$btn0 === $btn1

Also, if you use something, such as $btn1.remove(), then the element will not be freed until the next invocation of the $.single() method, which will remove it from the plugin’s internal collection object.

Another similar but more extensive plugin is the jQuery.fly plugin, which supports the case of being invoked with arrays and jQuery objects as parameters.

For more information on jQuery.single and jQuery.fly, you can visit http://james.padolsey.com/javascript/76-bytes-for-faster-jquery/ and https://github.com/matjaz/jquery.fly.

On the other hand, the jQuery implementation that handles the invocation of the $() method with a single page element is not complex at all and only creates a single simple object:

jQuery = function( selector, context ) {

    return new jQuery.fn.init( selector, context );

};

/*...*/

jQuery = jQuery.fn.init = function( selector, context ) {

    /*... else */

    if ( selector.nodeType ) {

        this.context = this[0] = selector;

        this.length = 1;

        return this;

    } /* ... */

};

Moreover, the JavaScript engines of modern browsers have already become quite efficient when dealing with short lived objects, since such objects are commonly passed around an application as method invocation parameters.

Summary

In this article, we learned some optimization techniques that can be used to improve the performance of jQuery applications, especially when they become large and complex.

We initially started with simple practices to write performant JavaScript code, and learned how to write efficient CSS selectors in order to improve the page’s rendering speed and DOM traversals using jQuery. We continued with jQuery-specific practices, such as caching of jQuery Composite Collection Objects and ways to minimize DOM manipulations. Lastly, we saw some representatives of the Flyweight pattern and took a look at an example of the Delegated Event Observer pattern.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here