jQuery Selector Performance Testing

Testing the chaining of jQuery find/children methods vs. chaining the selector of one method.

I recall reading somewhere recently that writing jQuery selectors was more efficient when written $('.class-name').find('.sub-class-name').find('.some-other-class-name') as opposed to $('.class-name .sub-class-name .some-other-class-name'). Not knowing exactly how these selectors are implemented in jQuery, I couldn't say for myself whether this was actually true or not. It seems counterintuitive to me, so I figured I would test it out before I changed all of my code to chain multiple find and children methods instead of chaining the selector of one find.

So, I threw together a quick little experiment to see for myself which way is faster.

I created a simple HTML file with a couple of wrapper divs with two large (1000 <li>) lists to modify.

  
    <!doctype html>  
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

      <title>Performance Testing</title>
    </head>

    <body>
      <div class="container">
        <div class="main">
          <ul class="list-1">
            <li class="odd">item</li>
            <li>item</li>
            <li class="odd">item</li>
            <li>item</li>
            ...
            <li class="odd">item</li>
            <li>item</li>
            <li class="odd">item</li>
            <li>item</li>
          </ul>
        
          <ul class="list-2">
            <li class="odd">item</li>
            <li>item</li>
            <li class="odd">item</li>
            <li>item</li>
            ...
            <li class="odd">item</li>
            <li>item</li>
            <li class="odd">item</li>
            <li>item</li>
          </ul>
        </div>
      </div>

      <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script>
      <script src="js/profiling/time.packed.js"></script>
      <script src="js/script.js"></script>
    </body>
    </html>
  

I then wrote a simple script to select various combinations of elements and do some timing. I used Remy Sharp's time.js (http://remysharp.com/2007/04/20/performance-profiling-javascript/) to easily track how long each operation took.

  
    $(document).ready(function() {
      // Wrap each $ call with the time method
      $ = time.func($);
  
      for (var i = 0; i < 10; i++) {
        // List 1
        $('.container .main ul.list-1 li').css('color', 'red');
        $('.container').find('.main').find('ul.list-1')
             .find('li').css('color', 'blue');
        $('.container').children('.main').children('ul.list-1')
             .children('li').css('color', 'purple');
        $('.container').children('.main').children('ul.list-1')
             .children('.odd').css('color', 'green');

        // List 2
        $('.container .main ul.list-2 li').css('color', 'red');
        $('.container').find('.main').find('ul.list-2')
             .find('li').css('color', 'blue');
        $('.container').children('.main').children('ul.list-2')
             .children('li').css('color', 'purple');
        $('.container').children('.main').children('ul.list-2')
             .children('.odd').css('color', 'green');
  
        // Both Lists
        $('.container .main ul li').css('color', 'red');
        $('.container').find('.main').find('ul').find('li')
             .css('font-size', 'blue');
        $('.container').children('.main').children('ul')
             .children('li').css('color', 'purple');
        $('.container').children('.main').children('ul')
             .children('.odd').css('color', 'green');
  
      }
      time.report();
    });
  

The JavaScript simply goes over each list individually and does some selecting and modifying, and then over both lists together. I was surprised to see that the three highest times from all the tests came from the chained selectors in one find call.

The results:

 

 

  1. 3.3 $('.container .main ul.list-1 li')
  2. 1.0 $('.container').find('.main').find('ul.list-1').find('li')
  3. 1.3 $('.container').children('.main').children('ul.list-1').children('li')
  4. 0.9 $('.container').children('.main').children('ul.list-1').children('.odd')
  5. 1.9 $('.container .main ul.list-2 li')
  6. 0.7 $('.container').find('.main').find('ul.list-2').find('li')
  7. 0.7 $('.container').children('.main').children('ul.list-2').children('li')
  8. 0.7 $('.container').children('.main').children('ul.list-2').children('.odd')
  9. 1.7 $('.container .main ul li')
  10. 0.8 $('.container').find('.main').find('ul').find('li')
  11. 0.8 $('.container').children('.main').children('ul').children('li')
  12. 0.8 $('.container').children('.main').children('ul').children('.odd')

 

 

Chaining multiple methods together... is faster.

Because of the fact that the selector engine (sizzlejs.com) parses from right to left, it parses far more than it has to when the selectors are chained together. When they are split into separate method calls, it essentially parses from left to right, narrowing the collection as it goes.

So, what does this mean for you?

Chaining multiple methods together as opposed to chaining the selectors in one method is faster. If you're not getting huge amounts of traffic on your site, the difference is so negligible that you'll probably never notice. However, sometimes tiny performance issues can get really big, really fast when they begin to scale. Just ask Twitter.