Mike Grace
Mike Grace

Reputation: 16924

More efficient jQuery sort method for DOM elements based on numerical attribute

I have some simple jQuery written to sort some elements based on a numerical attribute as illustrated at http://jsfiddle.net/MikeGrace/Vgavb/

// get array of elements
var myArray = $("#original div");

// sort based on timestamp attribute
myArray.sort(function (a, b) {

    // convert to integers from strings
    a = parseInt($(a).attr("timestamp"), 10);
    b = parseInt($(b).attr("timestamp"), 10);

    // compare
    if(a > b) {
        return 1;
    } else if(a < b) {
        return -1;
    } else {
        return 0;
    }
});

// put sorted results back on page
$("#results").append(myArray);

It works fine but I don't think it will scale because a total of 185 jQuery calls are made, 184 of them which are getting the attribute of an element to do the comparison.

What is a more efficient way to do this sorting with jQuery?

Upvotes: 9

Views: 15571

Answers (5)

Erik Philips
Erik Philips

Reputation: 54628

Using jQuery 1.6 Data() will be much faster.

First:

The data- attributes are pulled in the first time the data property is accessed and then are no longer accessed or mutated (all data values are then stored internally in jQuery).

This means accessing the specific data value after the first is internal so no DOM lookup nor parsing of the value needs to be done.

Second:

Every attempt is made to convert the string to a JavaScript value (this includes booleans, numbers, objects, arrays, and null). The string value "100" is converted to the number 100.

Now that the value is stored internally as a number, it does not need to be parsed every time either.

Html

<div id="original">
  <div data-timestamp="99">99</div>
  <div data-timestamp="999">999</div>
  <div data-timestamp="12">12</div>
  <div data-timestamp="11">11</div>
  <div data-timestamp="10">10</div>
  <div data-timestamp="9">9</div>
  <div data-timestamp="8">8</div>
  <div data-timestamp="7">7</div>
  <div data-timestamp="6">6</div>
  <div data-timestamp="5">5</div>
  <div data-timestamp="4">4</div>
  <div data-timestamp="3">3</div>
  <div data-timestamp="2">2</div>
  <div data-timestamp="1">1</div>
  <div data-timestamp="9999">9999</div>
</div>

Javascript

// sort based on timestamp attribute
myArray.sort(function (a, b) {

    // convert to integers from strings
    a = $(a).data("timestamp");
    b = $(b).data("timestamp");
    count += 2;
    // compare
    if(a > b) {
        return 1;
    } else if(a < b) {
        return -1;
    } else {
        return 0;
    }
});

Update jsFiddle Example

Sure your loops are the same, but much less processing per loop.

Upvotes: 3

Remember Monica
Remember Monica

Reputation: 3939

If your comparison function is expensive, the standard method is the schwartzian transform (seen in some comments, but without an actual example):

  1. extract the sort keys into [key, object] pairs
  2. sort by using the extracted keys
  3. extract the original objects

Example:

Array.prototype.schwartzian_sort = function (key_of) {
   var i;

   for (i = 0; i < this.length; ++i)
      this[i] = [key_of (this[i]), this[i]];

   this.sort (function (a, b) {
      a = a[0];
      b = b[0];

      return a < b ? -1
           : a > b ?  1
           :          0;
   });

   for (i = 0; i < this.length; ++i)
      this[i] = this[i][1];

   return this;
}

Instead of a comparison function, you pass a function that extracts srings or numbers:

myArray.sort(function (e) {
   return parseInt ($(a).attr ("timestamp"), 10);
});

This calls the parseInt and attr only once per element, which is always less than if called in a comparison function.

Upvotes: 1

Mike Grace
Mike Grace

Reputation: 16924

If you need to do lots of sorting repeatedly, it would be good to work towards a more native approach that doesn't involve jQuery. Otherwise, there really isn't much that is going to make a noticeable difference.

Here are some tests trying a few different approaches -> http://jsperf.com/jquery-sort-by-numerical-property/2

Upvotes: 4

sdleihssirhc
sdleihssirhc

Reputation: 42496

For what it's worth, I don't think you need to call the slow parseInt:

a = parseInt($(a).attr("timestamp"), 10);
a = +$(a).attr("timestamp");

If your timestamp attribute really is just a number, I think these will yield the same value.

Upvotes: 2

Nick Craver
Nick Craver

Reputation: 630429

If you're worried about performance, and know for sure where the attribute comes from, you can use the DOM methods directly, .getAttribute() in this case:

a = parseInt(a.getAttribute("timestamp"), 10);
b = parseInt(b.getAttribute("timestamp"), 10);

You can test the difference here, as you see it's more than a little speed boost, .attr() does a lot of extra work internally, ultimately calling .getAttribute() itself in this case anyway.

Upvotes: 6

Related Questions