views:

411

answers:

6

I'm having huge variations in performance depending on how I express my selectors. For example, look at these 2 selectors, which select exactly the same elements:

A) someTableRow.find("td.someColumnClass").find("span.editMode").find("input")
B) someTableRow.find("td.someColumnClass span.editMode input")

I would expect to B) to be faster as there is only 1 call, but in fact I'm finding A) executes around 8 times faster. I have no idea why, anyone have any insight? Thanks

+1  A: 

A is more calls, but simpler. B is one call, but more complex. In this case, the complexity of the call greatly out-weighs the quantity of calls.

Jonathan Sampson
+2  A: 

Because you are reducing the context for the search.

In case B, it has to search through every DOM element to see if it meets the criteria.

In case A, is can quickly decide to ignore anything that's not "td.someColumnClass", then it can take that subset of the DOM and ignore anything that's not in "span.editMode". So it has a much smaller set of elements to search through to find "input"s now.

Josh Wright
How can it "quickly decide to ignore ..." without going through the whole DOM children of someTableRow? Both of them check the same full set of children elements, one looks for td's with someColumnClass, then parses that list for spans with editMode, then parses that list for input. The other looks for all three criteria in the same search of the initial list.
Bill James
Since these two selectors are identical in meaning, I don't see why jQuery couldn't use the same strategy in both cases.
Nathan Long
A: 

There's a really interesting article on selector performance here: http://blogs.atlassian.com/developer/2009/08/jquery%5Fbondage.html

In it, the author shows a "bind" jQuery extension that shows how many times the function is evaluated.

bryanbcook
The asker is referring to performance of `find()`, not `bind()`.
Crescent Fresh
+1  A: 

The way JQuery handles selctors is a little different to CSS in my experience.

Reducing the context to the search is the key as Josh pointed out.

I find using the two parameter selectors really quick

How does this compare speed wise?

You don't need all the vars here it is just to make clear what I am doing.

var columnClasses = $('.someColumnClass');
var tableCell = $('td', columnclasses);
var editMode = $('.editmode', tableCell);
var spanInside = $('span', editMode);
var inputFinally = $('input', spanInside);

Kindness,

Dan

Daniel Elliott
+7  A: 

Presuming you are using at least jQuery 1.3 (i.e. with the addition of Sizzle), the performance you are seeing is due to the change in which the DOM is traversed. From here:

Up to and including jQuery 1.2.6 the selector engine worked in a "top down" (or "left to right") manner. jQuery 1.3.x (ie, Sizzle, which jQuery embeds) introduced a "bottom up" (or "right to left") approach to querying the DOM.

In your second example ("td.someColumnClass span.editMode input"), Sizzle effectively does this:

  1. get all input elements inside someTableRow
  2. for each input element found, traverse its ancestor tree for span elements with class="editMode". Remove input elements that don't have these ancestors
  3. for each span.editMode element found, traverse its ancestor tree for td elements with class="someColumnClass". Remove input elements that don't have these ancestors

In your first example however, your are qualifying each step explicitly with each call to find(), defining a context and traversing down from there. You are enforcing the "top-down" approach. It is equivalent to passing in a context at each step, which is generally considered a performance booster:

$('input', $('span.editMode', $('td.someColumnClass', someTableRow)))
Crescent Fresh
Thanks crescentfresh, makes sense. We have actually just moved to 1.3.2 from 1.2.6 and I was confused why some previously decently quick selectors became slower (most were faster). Question - which is quicker, passing in a context at each point, or using chained find() calls?
JonoW
They are effectively equivalent. $('foo', 'bar') actually gets re-routed to $('bar').find('foo') in the belly of jQuery. I suppose calling find() explicitly saves a couple CPU cycles, but nothing to blink at. Do whatever is most readable to your team. I find find very readable ;)
Crescent Fresh
Cool, I feel find() is more intuitive for me, so will use that
JonoW
+1  A: 

I did some research on jQuery Selector Performance myself. A big problem are lookups by classname on Internet Explorer. IE doesnt support getElementsByClassName - therefore jQuery and other frameworks "reimplement" it in JavaScript by iterating through all DOM elements. Check out the following analysis blog about jQuery Selector Performance

Andreas Grabner