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:
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:
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.
Want to use code from this post? Check out the license.