jQuery.fn.each vs. jQuery.fn.quickEach

JavaScript performance comparison

Revision 63 of this test case created by Andy Harman

Info

The quickEach method will pass a non-unique jQuery instance to the callback meaning that there will be no need to instantiate a fresh jQuery instance on each iteration. Most of the slow-down inherent in jQuery’s native iterator method (each) is the constant need to have access to jQuery’s methods, and so most developers see constructing multiple instances as no issue… A better approach would be quickEach.

Added "quickerEach" implementation (slightly modifies the original quickEach code).

Note that the performance is VERY DIFFERENT when the loop actually does something with the jQuery object. When you use "addClass" in the loop then "quickerEach" performs best, when it is not there, the for-loop performs best. It feels like the JS runtimes are able to realise that the for-loop doesn't actually need to do anything - and optimises for that scenario. That's really clever - but it makes measuring performance difficult.

Finally figured-out while-loop that beats quickerEach. Unfortunately inlining everything in the while's condition may reduce its usefulness.

Preparation code

<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js">
</script>
<script>
  var a = $('<div/>').append(Array(100).join('<a></a>')).find('a');

  jQuery.fn.quickEach = (function() {
    var jq = jQuery([1]);
    return function(c) {
      var i = -1,
          el, len = this.length;
      try {
        while (++i < len && (el = jq[0] = this[i]) && c.call(jq, i, el) !== false);
      } catch (e) {
        delete jq[0];
        throw e;
      }
      delete jq[0];
      return this;
    };
  }());

  jQuery.fn.quickerEach = (function() {
    var jq = jQuery([1]);
    return function(c) {
      var i = -1,
          el;
      try {
        while (el = jq[0] = this[++i] && c.call(jq, i, el) !== false);
      } catch (e) {
        jq[0] = 1;
        throw e;
      }
      jq[0] = 1;
      return this;
    };
  }());
</script>

Preparation code output

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
.each()
a.each(function() {
  $(this).addClass("wibble");
});
pending…
.quickEach()
a.quickEach(function() {
  this.addClass("wibble"); // jQuery object
});
pending…
for loop
var jq = jQuery([1]);
var length = a.length;
for (var i = 0; i < length; i = i + 1) {
  jq[0] = a[i];
  jq.addClass("wibble"); // jQuery object
}
pending…
.quickerEach()
a.quickerEach(function() {
  this.addClass("wibble"); // jQuery object
});
pending…
while loop
var jq = jQuery([1]), i = -1;
while (jq[0] = a[++i]) {
  jq.addClass("wibble");
}
pending…
while without body
var jq = jQuery([1]), i = -1;
while (jq[0] = a[++i] && jq.addClass("wibble"));
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

1 comment

Andy Harman (revision owner) commented :

SORRY - I'm an idiot!

This test case contained a major bug. The quickerEach implementation (and while without body depended on the following code:

while (el = jq[0] = this[++i] && c.call(jq, i, el !== false);

I forgot that operator precedence would cause this to be evaluated as (note the added-brackets):

while (el = jq[0] = (this[++i] && c.call(jq, i, el !== false) );

So the addClass method was chewing on a boolean rather than a DOM element.

Add a comment