Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with:

Your jQuery Selector Context Can Be A jQuery Object

By Ben Nadel on

This is just a quick post to clear up any confusion over what kind of objects can be used as a context when performing a jQuery selector execution. Sometimes, when reviewing jQuery code, I see people make data type conversions in their context usage:

  • $( "div", myStuff[ 0 ] )

Here, you can see that the programmer is converting their "myStuff" jQuery collection into a single DOM node to be used as a selection context. This might make sense if the myStuff collection contained nodes within which you did not want to search; but more often than not, this data type conversion is entirely arbitrary. And, more than that, it is unnecessary, and inefficient.

If you look at the documentation for the $() method, you will see that the context argument can be a DOM element, a document, or a jQuery object. And, if you check the actual source code, you will see that the use of the context:

  • $( selector, context )

... is actually turned into a find() method internally:

  • $( context ).find( selector )

To optimize this execution, jQuery actually checks to see if the given context is a jQuery object; if it is, it performs the find() method call directly on the given context. If the given context is a DOM element, however, it first converts it to a jQuery object and then executes the find() method. So, actually, if you extract a DOM node as the context from a given jQuery object, you're slowing down execution, forcing jQuery to re-package the DOM node as a jQuery object.

When using a context, I personally find the find() method to be easier to read; but, if you're going to use the context argument in the $() method, just remember that it can be a jQuery object; there's no need to pass in a DOM element if you don't already have one.



Reader Comments

@Krishna,

I believe what Brandon is referring to is the actual "context" attribute of the jQuery collection. This is somewhat of a "behind the scenes" property.

What I'm referring to is the context used in the $() method. Brandon points out that this does become $().find() under the hood; all he's saying is that the type of context passed has a different effect on the context property. As far as why the difference, I'm not really sure.

As far as performance is concerned, yes, it appears there are differences in 1.3 and 1.4 versions of the library. 1.4 seems to take more variety into account, probably optimizing for more use cases.

In jQuery 1.4, it definitely tries to use the existing jQuery object before it checks for a raw DOM element (which it then converts to a jQuery object).

In jQuery 1.3, the init() method appears to be much less optimized, simply calling:

jQuery( context ).find( selector );

... in this case, you might be right about using a raw DOM element as slightly faster as this will be the more slightly optimized case.

In any case, however, you can use either a jQuery collection or a DOM element.

Reply to this Comment

If you *know* you're dealing with a jQuery object, always do:

context.find(selector)

over:

$(context).find(selector)
- or -
$(selector, context)

IMO, passing in a jQuery object as the context is only useful if you've written code where the context could be either a DOM node or a jQuery object, because the function/method is used multiple ways. Whenever possible, I like my code explicit in what the context type because there is some overhead in jQuery when it comes to initializing new jQuery objects. If you don't have to initialize a new object, don't do it.

Reply to this Comment

@Dan,

I agree regarding context.find(). To me, that's the most readable way to do it; and, since the majority of times (for me), my context is already a jQuery collection, it's what makes the most sense from performance.

jQuery 1.4.2 seems to use this approach when possible over the raw DOM element - but you still get the extra method call involved.

Reply to this Comment

There is one case in which you need a DOM node as the "context" argument: when you're using it for the .live() method. Otherwise, you're right.

Reply to this Comment

@Ben,

Yeah. By default, .live() binds events to document and uses event delegation to see if $(event.target).closest(selector) exists before executing your function.

As of jQuery 1.4, you can specify a DOM element to bind to instead of document, which limits the possible elements that trigger the event.

More info here: http://api.jquery.com/live/#event-context

(hope that makes sense. I'm typing quickly while at work thinking about a bunch of other stuff. :) )

Reply to this Comment

My feeling is that using a jQuery object as the context argument only hinders readability, and the performance will be slightly worse (however negligible) because of that extra internal conversion. I sometimes see $("expr", cachedjQueryObject) being interpreted as $("expr").andSelf(), rather than $(cachedjQueryObject).find("expr"). The latter issues a command and is more procedural/self-documenting.

I tend to use the context parameter only when inside an anonymous callback function and the context will be "this". IMO, $("span", this) is just as readable as $(this).find(), but that could just be my syntax highlighting :)

Reply to this Comment

My understanding is that the context parameter behaviour has changed in jQuery 1.4, and it was not good practice prior to pass the jQuery object as a context. Cody Lindley notes at the beginning of chapter 9 of 'jQuery enlightenment' (for 1.3.2) about passing context -

'For a performance gain to be made, you need to pass an actual DOM reference as the second parameter. Passing the jQuery object itself - e.g. $('#context') - still requires a search of the entire document for the reference. Searching the entire document completely negates the point of passing a context.'

So until 1.4, it was necessary (unless Cody was wrong).

Cheers,

Colin

Reply to this Comment

@Eric,

I tend the prefer the $( ... ).find() approach; but that might just be because I typically have a jQuery collection to work with at the time. As you are saying though, when in a callback, I will also use the "this" reference as the context node and it works quite nicely.

@Colin,

I'll have to take a look at the jQuery source code for 1.4.2 to be sure; I'll get back to you on that matter.

As far as what Cody Lindley was saying, I think perhaps what he meant was that you simply don't want to be performing jQuery-look-ups as *part* of the context definition. Meaning, if you have a jQuery collection already (and therefore have DOM nodes within it), then you can use it; but, don't use a jQuery collection if you have to look up the nodes to define the context meaning.

I could be misunderstanding, but I think about it like this - imagine I have some callback that passes in a jQuery collection:

doSomething( myCollection ){
return( $( ".target", myCollection ) );
}

... here, I am using the given jQuery collection (myCollection) as the context. I believe this is ok since the jQuery collection has already searched the DOM and collected the given DOM nodes.

However, I think what Cody's saying is that you want to avoid things like this:

doSomething(){
return( $( ".target", $("#parent") );
}

... since jQuery had to perform two searches - one for Parent and one for Target. If you have that kind of a setup, then you can probably just roll it into one search:

$( "#parent .target" );

... this way, Sizzle can really optimize the lookup.

Reply to this Comment

Not sure about that - at other points in his book he explicitly uses the following convention:

$('a', $('#context')[0])

Not saying for sure this is correct, but certainly the code you are seeing with this syntax could very well be the result of expert advice - I know I used that convention specifically after reading this book.

Cheers,

Colin

Reply to this Comment

@Colin,

I just took a look in the jQuery 1.4.2 development (non-minified) version and the init() method which handles the jQuery collection creation looks like this (minus a LOT of code) ~Line 159:

// HANDLE: $(expr, $(...))
} else if ( !context || context.jquery ) {
. . . return (context || rootjQuery).find( selector );

// HANDLE: $(expr, context)
// (which is just equivalent to:
// $(context).find(expr)
} else {
. . . return jQuery( context ).find( selector ); }

It looks like the jQuery library still handles both versions. In the end, if the context object is not a jQuery object, you can see that internally, the code is still converting the context DOM node into a jQuery object.

So, whether you pass in a DOM node or a jQuery collection object as context, you still end up with a jQuery collection. It looks like both ways are equally performant - it just depends on what you are going to do with the context object after you pass it in as a context. Meaning, if you are going to use it externally as a jQuery object, you might as well convert it externally.

Reply to this Comment

You know a way to pass many contexts?

  • $('.test', $('#container-1, #container-2') ).show();

Tks

Reply to this Comment

contextFactory = function(selector) {
var ctx = $(selector);
return function(selector) {
return $(selector, ctx);
};
};

$popup = contextFactory(".jsPopupRegister");
$popup("a")

more readable IMO

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.