Traversal vs. Collection Filtering In jQuery

Posted February 17, 2010 at 9:50 AM by Ben Nadel

Tags: Javascript / DHTML

When it comes to jQuery selectors, I tend to think in two different modes: collection filtering and traversal filtering. By that, I mean that I see filtering as happening at two different and distinct times in the selection process. With collection filtering, jQuery filters nodes only once it has compiled them into a collection. All filtering done at that point is done in the context of the collection. With transversal filtering, on the other hand, jQuery filters nodes as it gathers them during DOM traversal. All filtering done at that point is done before a final collection has been established. This is my philosophical interpretation of what's happening - I don't truly know how this filtering is done at the technical level. That said, I have found this type of thinking to be very useful.

 
 
 
 
 
 
 
 
 
 

To demonstrate what I'm talking about, let's take a quick look at some HTML:

  • <table>
  • <tr>
  • <td>
  • <span>
  • <em>Hello</em>
  • </span>
  • </td>
  • <td>
  • <span>
  • <em>World</em>
  • </span>
  • </td>
  • </tr>
  • <tr>
  • <td>
  • <span>
  • <em>Hello</em>
  • </span>
  • </td>
  • <td>
  • <span>
  • <em>World</em>
  • </span>
  • </td>
  • </tr>
  • </table>

Given this HTML, imagine that I want to bold the EM element contained within the first SPAN of each table row (TR). Because I use the jQuery pseudo selector, ":first", so often, whenever I see the keyword "first", I tend to want to apply this filtering mechanism. As such, let's see what happens when I apply the ":first" selector to our SPAN element:

  • $( "tr span:first em" )
  • .css( "font-weight", "bold" )
  • ;

While this might sound like a good approach, when we run this selector, we get the following output:

Hello World
Hello World

As you can see, only the first EM in the table was bolded - not the first EM within each row. The mistake that we made here was using the wrong type of selector. The pseudo selector, ":first", is a collection filter; meaning, it filters nodes in the context of a collection, not in the context of DOM traversal. As such, jQuery collected all of the SPAN elements of each row into a single collection and then filtered them down to a single node.

What we need to do is use a traversal filter - one that filters in the context of the DOM. Unfortunately, jQuery does not provide any cousin-style traversal filtering (ie. "sibling" filtering across different parents). As such, we'll need to make our selector a bit more detailed. In this case, we'll need to add a TD selector that has, attached to it, some traversal filtering:

  • $( "tr td:first-child span em" )
  • .css( "font-weight", "bold" )
  • ;

As you can see, we've added the ":first-child" selector to the TD to contextualize the SPAN element. The pseudo selector, ":first-child", is a traversal filter that is applied in the context of the DOM. As such, the TD element is only filtered in the context of its parent TR, not in the context of the entire TD collection. In doing so, we have slightly altered the intent of our selector - making it dependent on the TD, not on the SPAN - but, we do end up with the desire output:

Hello World
Hello World

As you can see, the appropriate EM elements have been bolded. Only, it's no longer the EM contained within the first SPAN, it's the EM contained within the first TD. Like I said, we've had to slightly alter the intent of our selector; but, at least this demonstrates the difference in capabilities between collection and traversal filtering.

In most cases, it's not necessary to think too deeply about your jQuery selector choices; typically, they're so small that the difference between collection and transversal filtering becomes insignificant. But, in cases where your selectors get longer, the difference between these two modes of filtering becomes more relevant.



Reader Comments

Feb 17, 2010 at 12:32 PM // reply »
171 Comments

@Ben:

The problem I have with this entry is your two different selectors have completely different meanings.

In the first example you're not grouping by table cell at all, but the second one you are. Also, there's a difference between the behavior in :first and :first-child.

The :first filter grabs the first item in the collection of elements. The :first-child filter grabs the every match of the first one found.

I think this entry would be more clear if you used the following markup:

<div>
<div>
<span>
Hello
</span>
<span>
World
</span>
</div>
<div>
<span>
Hello
</span>
<span>
World
</span>
</div>
</div>

And then used selectors that are grabbing the same elements:

$( "div span:first em" )
.css( "font-weight", "bold" )
;

$( "div span:first-child em" )
.css( "background-color", "#ffcccc" )
;

(You can view the code here: http://jsbin.com/ozeke)

The main thing is understanding how the filters actually operate.


Feb 17, 2010 at 12:37 PM // reply »
11,314 Comments

@Dan,

Ha ha, Dan, sometimes I think we just completely miss each other on communication :) Yes, the two different selectors are completely different. I am not saying they do the same thing at all; in fact, I'm saying that they work at two different times and I even state that the intent of the second selector is not the same as the first:

In doing so, we have slightly altered the intent of our selector - making it dependent on the TD, not on the SPAN

I will try to make my language more clear in future posts.


Feb 17, 2010 at 1:57 PM // reply »
7 Comments

Excellent explanation about these two concepts of jQuery. Something more that I will need to keep in mind.


Feb 17, 2010 at 9:32 PM // reply »
4 Comments

hmm, I'm sure you're correct and all, but to me :first makes sense as only ever returning a single item, you can't have more than one 'first' thing in a collection, so my first instinct on looking at the example was something more like...

$('tr').each(function(){
$(this).find('span:first em').css("font-weight","bold");
});

...makes more sense to me in solving the problem as originally described, for each 'tr' we find the first spans em and bold it.

Probably not what you were trying to show though :)


Feb 17, 2010 at 9:56 PM // reply »
11,314 Comments

@Adam,

That will definitely work. I was only trying to demonstrate different ways to think about filtering; approaching this way is certainly a good way as well.


Jon
Feb 19, 2010 at 5:20 AM // reply »
1 Comments

Whilst we're skinning this cat:

$('tr').each(function(){
$('em',$(this)).first().css("fontweight","bold");
});


Feb 19, 2010 at 9:46 AM // reply »
171 Comments

@Jon:

Instead of:
$('em',$(this)).first().css("fontweight","bold");

You're better off using:
$('em', this).first().css("fontweight","bold");

- or -

$(this).find('em').first().css("fontweight","bold");

There's some overhead in calling $(), so if you can minimize the usage, the better off you'll be.

I personally like the find() method--I think it's more readable.


Feb 22, 2010 at 8:43 PM // reply »
11,314 Comments

@Dan,

I agree re: find(). It's very left-to-right readable.


Nov 24, 2011 at 10:53 AM // reply »
1 Comments

I really like this 'philosophical' interpretation of the way different jQuery selectors are working.

With jQuery it's very easy to come up with a dozen different ways of achieving the same thing (as several of the comments demonstrate). The difficulty is finding the 'best' way to get the job done, i.e. what is the most efficient set of selectors to use to get to the element(s) you want?

Given the title of this article I was hoping to find some form of testing and results demonstrating which method would be the quickest in a given example.

I'm specifically interested in the performance difference between a contextualised $() selector statement vs. a chained filter method. e.g. $('.someClass', '#myEl') vs. $('#myEl').filter('.someClass')

Any ideas?


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Jun 20, 2013 at 3:15 AM
A Billion Wicked Thoughts By Ogi Ogas And Sai Gaddam
nice post i love it thanks 4 u :) ... read »
seb
Jun 20, 2013 at 2:32 AM
Working With Inherited Collections In AngularJS
@mike, @ben, The best article about scope and prototypal prototypical inheritance in angularjs is http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical- ... read »
Jun 20, 2013 at 2:17 AM
ColdFusion NumberFormat() Exploration
Nice read thanks Ben, Is there a way to mask a negative number? Long story short in the finance sector when you go 'short' on a stock you want the price to fall this is a good thing because you are ... read »
Jun 20, 2013 at 1:09 AM
The Beauty Of The jQuery Each() Method
my html code : <html> <head> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="nss.js"> ... read »
Jun 19, 2013 at 11:31 PM
Directive Link, $observe, And $watch Functions Execute Inside An AngularJS Context
@Ben, bunch to learn indeed, but thats fun part : ) ... read »
Jun 19, 2013 at 10:41 PM
Referencing ColdFusion Query Columns In A Loop Using Both Array And Dot Notation
Burdock-roots Are you going fat day by day? You need to be good for your family and make some money too. So we bring for you a best product that helps you to be more energetic every day. You will b ... read »
Jun 19, 2013 at 9:52 PM
Working With Inherited Collections In AngularJS
I recognize the applicability of your solution, and how easy it makes to share data across multiple views or even "submodules" of rather simple application. But it seems to me that it creat ... read »
Jun 19, 2013 at 9:38 PM
Directive Link, $observe, And $watch Functions Execute Inside An AngularJS Context
@Alesei, Glad you like it. Even after working with AngularJS for months, I still get a bunch of unexpected, "$digest is already in progress". So hard to debug sometimes! ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools