Thursday, January 22, 2009

Beware jquery's :nth-child selector

The past day or two I've been using selectors like this to manipulate tabular data:
  • $("table#summary tr:nth-child("+k+")")
I was using the selectors to pump a table of audit data into a summary table, and the whole process was 5-6 seconds for under 50 rows. There were lots of ":contains" statements in there, so naturally I attacked those first, caching the results in .data() fields. Yet most of the lag time remained.
So I cranked on the Firebug profiler and profiled the function that does the heavy lifting. Calls to nth-child were taking up 4700ms out of about 5000ms total!
After staring at it for a minute, I recalled that :nth-child looks for all elements that are the nth child of their parent -- that's what I want, right? -- but it does not assume that they are direct descendants of the preceding element in the selection chain.
"Matches all elements that are the nth-child of their parent ... While :eq(index) matches only a single element, this matches more than one ..."
This is certainly what you need in some cases, but if your jquery consists of a set of siblings and you just want to grab a particular element by index, it's far quicker to do one of the following:
  • $("table#summary tr:eq("+k+")")
  • $("table#summary tr").eq(k)
Once I made that change, Firebug's Profiler timing went from 5000ms+ down to the 300-400ms range. Lesson learned.




3 comments:

vj said...

Excellent article, I was about to use the nth element to split a list into multiple lists, I will use eq.

Good to know that your blog is instantly indexed by Google.

Mike said...

Look at this statement
"After staring at it for a minute, I recalled that :nth-child looks for all elements that are the nth child of their parent -- that's what I want, right? -- but it does not assume that they are direct descendants of the preceding element in the selection chain."

Going off the selector you are using I thought it should be pointed out that tr elements are not children of the table element. They are grandchild. Even if you code it that way when the DOM is constructed it will add the parent node generally tbody. That's why you have to use a descendant selector instead of a child selector. So it could not assume the preceding was the parent because nth-child was used. The preceding element could be an ancestor or sibling or parent depending what the preceding selector was.

Unknown said...

Mike, I think you're mixing a couple things up. What you say would be true of the selector, "table > tr:eq(...)" but the selector I used above "table tr:eq(...)" is going to take the k'th tr that is a descendent of the designated table element. The nth-child problem is something different: it tells jquery to find any tr elements that are descendents of that table *and* are the k'th children of their direct parent element. That changes the operation from O(k) to O(n) which accounts for the increased processing time.

Hope that clears things up.