Performance based jQuery – Tips to speed up your code

jQuery Performance Tips

Always understand the full scope of the function you are using:
In the API, make sure you fully understand what the function does, what parameters can be passed, and how they can be passed.

e.g.: http://api.jquery.com/show/
The $.show([duration], [callback function]) function accepts two parameters, the duration and callback function. If you examine the $.show() function, you will see that the function will determine which parameter is present due to its type.

This means that

  • $.show(100) will work (and will show an element in 100 milliseconds)
  • $.show(doWorkFunction) will work (and call the doWorkFunction upon completion of showing an element after 0ms).
  • $.show() will just show the element with no delay and no callback function.

Selectors

http://api.jquery.com/category/selectors/
In order of most optimal (fastest)

1) ID Selector: $(‘#someId’)

  • Always descend from an ID.
    e.g. $(‘#someId’).find(‘.someClass’) or $(‘#someId’).find(‘a’)

  • Use .find() instead of context selectors (e.g.: $(‘#someId’).find(‘.someClass’) instead of $(‘.someClass’, ‘#someId’) or $(‘#someId .someClass’) )
  • Selecting from an ID is always the #1 fastest way to select an element, as this is a built in native javascript call behind the jQuery function.
  • Since jQuery sizzle selector engine reads right to left, it is BAD to prepend an element type to an id selector, as it will first return every matching type, then find the id from the subset returned. (The opposite is true for classes & attributes, will show later)
    e.g.: $(‘input#someId’)
  • Similarly, it is bad to prepend a class or element tag to an id selector, as it will slow it down.
    e.g.: $(‘.someClass#someId’), $(‘input#someId’)

2) Selecting by element type is the 2nd fastest lookup, since it also has native javascript support: $(‘input’) or $(‘div’).

  • Note: This should still use an ID for context for the most part, unless performing an action for every single specified element on the page.
    e.g.: $(‘#someId’).find(‘input’)

3) Class selector is among the slowest selectors for browsers that don’t have native JS support; browsers using ECMAScript 4 and below, that don’t support document.getElementsByClassName or document.querySelector. In IE it loops through entire DOM due to lack of JS support.
More Info: http://stackoverflow.com/questions/3058208/jquery-selectors-by-css-class-in-ie-is-really-slow-workarounds).

  • When possible, use context (define a parent) to start your search from.
    e.g.: $(‘#someId’).find(‘.someClass’); instead of just $(‘.someClass’);
  • If there isn’t a parent ID to select from (which their should be, but if not) always prepend an element to the class selector.
    e.g.: $(‘input.someClass’) or $(div.someClass’)

4) Attribute selectors: Should really only be used with some sort of context, whether it is an element or id.

  • An attribute selector without context would search entire DOM to find a match.
    e.g. $(‘[value=true]’) will look up an value attribute equal to ‘true’
    $(‘[data-name]’) will look up any attribute in document with a data-name attribute
  • Should give some context to find a more specific result set
    e.g. $(‘#someId’).find(“input[value=true]’)

5) Pseudo-Selectors are very slow and require a lot of time, but can be an easier way to solve problems.

  • Pseudo-selectors aren’t native to JS, they are matched by regex engine within Sizzle.js (component of jQuery). The example below, the p-selector is slower than the following 3.
    e.g. $(‘:button’) gives you the same result (in most cases) set as $(‘button’), $(‘input[type=button]’) and $(‘input[type=submit’), so use the latter.
    e.g. To find an attribute that begins with certain text ‘some____’: $(‘[name^=some]’)

A full list of jQuery selectors can be found on their site:
http://api.jquery.com/category/selectors/

Object Caching:

When using a jQuery object more than once it is best practice to cache the object so you aren’t making several redundant DOM traversals/lookups.

Object caching. Example 1a: Bad
/*This performs a lookup each time you use the jQuery selector notation $('#emailField'), which gets quite expensive*/
function doStuff() { var emailValue = $(‘#emailField’).val(); $(‘#emailField’).prop(‘disabled’, true); $(‘#emailField’).val(‘’); $(‘#emailField’).addClass(‘inactive’); }

In this function the $(‘#emailField’) selector is called in three separate instances, in each instance, the client has to wade through the DOM to find the specified ID.
Instead it is better to do the following

Object caching. Example 1a: Good
/*Caching the element returned by the selector can reduce each call by over 90%*/
function doStuffBetter() { var $emailField = $(‘#emailField’), emailValue = $emailField.val(); $emailField.prop(‘disabled’, true); $emailField.val(emailValue + ‘!!!’); $emailField.addClass(‘inactive’); }

Similarly, when in loops or event handlers, caching the $(this) object also provides better results:

Object caching. Example 1b: Good
$(‘#emailField’).each(function(i){ var $thisEmailField = $(this), emailValue = $thisEmailField.val(); $thisEmailField.prop(‘disabled’, true); $thisEmailField.val(emailValue + ‘!!!’); $thisEmailField.addClass(‘inactive’); });

(…can optimize this more using chaining, and native javascript… keep reading)

jQuery Function Chaining:

The above examples are good, but can be better. Several of the jQuery functions can be ‘chained’ or concatenated to improve performance.

NOTE: Not all jQuery functions can be chained, so don’t assume that they can without testing.

Improving on our previous example:

jQuery method chaining. Example 1c: Good
function doStuffEvenBetter() { var $emailField = $(‘#emailField’), emailValue = $emailField.val(); //Chain functionality where possible $emailField.prop(‘disabled’, true).val(emailValue + ‘!!!’).addClass(‘inactive’); }

As you can see this not only speeds up the call, but makes the code much more concise.

(We can still do better!)

When to use jQuery, when to use vanilla Javascript

There is a time and a place for jQuery, otherwise, stick to native Javascript:
(Take a look at increasing performance by putting down jQuery in place of Javascript: http://blog.webdevinci.com/increase-site-performance-by-cutting-down-on-jquery-back-to-javascript-basics/)

jQuery’s slogan is ‘Write Less, Do More’. This is very accurate, as Javascript can take up some space at times. But not fully understanding how jQuery functions handle certain things can cost you in performance. Sometimes native javascript can yield the same result in a much faster manner.

One of the most common times to substitute jQuery with native javascript are in $.each() loops and jQuery event handler callback functions. Another is when you are selecting an element, using document.getElementById(‘id’) is a bit longer than $(‘#id’), but the prior is up to 98% faster depending on where it lies in the DOM. (Generally, the deeper in the DOM, the longer it takes for jQuery)

Here is a list of very common times to use native JS rather than jQuery:

1) this instead of $(this):
Understand that these two objects are different in the sense that the first is a javascript object (and can only use javascript functions) and the second one is a jQuery object (and can only use jQuery functions).

Javascript alternatives for jQuery
/*Vanilla Javascript will ALWAYS outperform jQuery*/
$(‘input’).each(function(i){ var thisJqId = $(this).attr(‘id’); var thisJsId = this.id; var thisJqClass = $(this).attr(‘class’); var thisJs = this.className; var thisJqValue = $(this).val(); var thisJsValue = this.value; var thisJq = $(this).prop(‘selected’);//or even slower: $(this).is(‘:selected’); var thisJs = this.selected; var thisJqChecked = $(this).prop(‘checked’);//or even lower: $(this).is(‘:checked’); var thisJsChecked = this.checked; var thisJqChecked = $(this).prop(disabled);//or even slower: $(this).is(‘:disabled); var thisJsChecked = this.checked; });

2) Javascript selectors instead of jQuery
jQuery simplifies selecting DOM elements, and are very useful sometimes. But when doing something that has known support in all browsers, and known to be 90+% faster, you should use it. For example, instead of
$(‘#someElement’) , $(‘#someElement’).val()
use
document.getElementById(‘someElement’) , document.getElementById(‘someElement’).value

For those concerned with nulls being returned, create a simple wrapper function to emulate what jQuery does (but faster) in your utils file that handles that:

Javascript wrapper function to get no nulls returned
function getElementByIdNoNull(element) { if(element !== null) { return element.value; } else { return ‘’; } }

There are a few more that can be used listed here:
http://stackoverflow.com/questions/4651923/when-to-use-vanilla-javascript-vs-jquery

Using vanilla JS selectors can yield a 95% performance boost!

Note: The jQuery .each() function doesn’t seem to deteriorate until around 1000 iterations. Javascript for loops should be used when iterating over a large result set, otherwise .each() seems to perform equally as well. However, only use $.each to iterate over jQuery result sets.
http://stackoverflow.com/questions/3074226/how-to-using-for-instead-of-each-function-in-jquery

The best way to find out which is faster (native js vs jQuery) is to do time tests. You can do this by adding a ‘stopwatch’ around the function you are testing.

Javascript wrapper function to get no nulls returned
console.time(‘timer1’); $(‘input’).each(function(i) { console.log(i); }); console.timeEnd(‘timer1’); /////////////////////////////////// console.time(‘timer2’); var $input = $(‘input’), inputLength = $input.length; for(var i = 0; i < inputLength; i++) { console.log(i); } console.timeEnd(‘timer2’);

jQuery Event Handlers: Binding vs Delegating

Event Binding: This binds a listener directly to the element that you want to listen to events for. Useful for specific buttons or links that need an action associated with them.

There are two ways to do this:
1) .on() function (introduced in 1.7)
The .on() function is the bottom-line function for attaching event listeners to DOM elements. Meaning that other functions that attach listeners such as .bind(), previously .live() and .delegate(), and shortcuts such as .click(), .keypress(), .hover() are all using the .on() function to attach the listener.

That being said, the .on() function is the fastest way to attach a listener and also yields faster execution time than other binding functions.

jQuery click listener
$(‘#someButton’).on(‘click’, function(event) { callSomeFunction(); });

2) Shortcuts (Not recommended): .click(), .hover() function is a super-set function of .on()
This is a shortcut to bind a handler directly to an element. However, as of jQuery 1.7 the .on() function was introduced and made all of the shortcut bindings (.click(), .hover(), .mouseout(), etc) inadequate, as it requires more function calls, has a ternary operator, and is overall more slow on binding and when executing for the first time.

jQuery Event Delegation: Event delegation is used for creating listeners for multiple items or often used to attach event listeners to elements that don’t yet exist in the DOM. I will explain the difference between this and binding in the following example.

jQuery click listener
/*The following is binding linkToListener class to doStuff() when clicked. */
$(‘.linkToListen’).on(‘click’, function(){ doStuff(); //Not going to work without element present in DOM. });

However, if .linkToListen doesn’t exist in the DOM, this listener will be ignored. You can use proper delegation to add the listener to each of the .linkToListen classes, which don’t have to be in the DOM right now, but can come in as dynamic content later:

jQuery click delegation
/*The element that isn't in the DOM yet should be put in as the 2nd paramater of the $.on method*/
$(‘#parentElementThatDoesExist’).on(‘click’, ‘.linkToListen’, function(){
/*Will work once element is introduced to DOM
doStuffForReal(); });

You aren’t binding the listener to the .linkToListen elements, but to the #parentElementThatDoesExist element. You must bind to some element that does exist first, then add the element that you wish to delegate listening to in the second parameter of the .on() function.

It is best practice to attach the listener to an ID that exists in the DOM rather than flooding the $(document). If there is no parent element close by to attach to, you should first try to create one. If that is not possible, it should be very last resort to attach the listener to the document.

jQuery click delegation
$(document).on(‘click’, ‘.linkToListen’, function(event){ event.stopPropogation();
/*This will surefire work whenever the element is introduced*/
doStuffForRealForReal(); });

A more practical example, having delegation for each table cell of a table. The listener is binded to the table, but anytime a table cell is clicked, the event will ‘bubble’ up to the table node and execute the callback function.

jQuery click delegation
$(‘table’).on(‘click’, ‘td’, function(){ alert(‘Cell Clicked’); });

Summary: Event delegation is good for elements that aren’t expected in the DOM at document.ready time. It is also good to use for an element that will be present in multiple places (i.e. the table cell example above) since you are only binding to one table element instead of every single cell. From here the action performed on the cell bubbles up to the table level and calls the callback function.

Helpful Links

Comments are closed.