Understanding the Context in jQuery

Wednesday, June 24, 2009

When selecting elements jQuery has an optional second argument called context. This context provides a means to limit the search within a specific node. This is great when you have a very large DOM tree and need to find, for example, all the <a> tags within a specific part of the DOM tree. I’ve seen various articles, usually performance related, stating that you should be using a context for your selectors. While it is true that using a context for your selectors can potentially boost performance, most of these articles misuse the context.

Finding the Context

As of jQuery 1.3 the context is exposed as a property on the jQuery collection. Here is how you can check to see what the context is.

$('a').context; // => document

Running the above code shows that the context is the document element. In fact, the default context for all jQuery collections is the document in which jQuery was loaded. In other words, selectors are run against the whole document by default.

Changing the Context

The context needs to be a node to work properly. This is the part that is often overlooked. I’ve overlooked it in the past as well. Lots of examples out there tell you to just pass a second selector to jQuery to act as the reference node for the search. While this seems to work, it is still running the search on the whole document.

Here is an example of passing a second selector as the context. By looking at the context property you can see that the context is still the document though.

$('a', '#myContainer').context; // => document

When jQuery encounters another selector for the context it actually converts it to say the following instead.

$('#myContainer').find('a');

This conversion also happens the same way if you pass a jQuery collection as the context.

Now lets actually look at how we can change the context for the jQuery collection.

// get the node for the context
var context = $('#myContainer')[0];

// pass the context as the second argument
$('a', context).context; // => <div id="myContainer">

From this example we can see that passing a node as the second argument actually changes the context for the jQuery collection.

jQuery 1.3.3 and .live()

In jQuery 1.3.x, the .live() method binds its event handlers to the document. In the upcoming jQuery 1.3.3 release the .live() method will bind its events to the context of the jQuery collection. This means your .live() events can be more focused and potentially faster.

Posted in jQuery with 23 comments

Comments

It sounds to me that the real problem is that the second argument is misnamed, or that $('a', '#myContainer').context returns the wrong thing.

By Ike on Wednesday, June 24, 2009 at 11:44 PM

@Ike or that maybe when the context is a selector or jQuery collection it should take the first matched element instead of doing what it currently does.

By Brandon Aaron on Thursday, June 25, 2009 at 01:07 AM

Scope content issues aside, what are the differences in performance between $('a', '#myContainer') and $('#myContainer a') ?

By Bryan Buchs on Thursday, June 25, 2009 at 02:09 PM

@Bryan Buchs, It is faster to use $('#myContainer').find('a'). jQuery uses the Sizzle selector engine which runs queries from right to left, like a browser does. With that in mind you’ll want to optimize the right side of your selector to be the quickest.

By Brandon Aaron on Thursday, June 25, 2009 at 02:23 PM

This has always puzzled me from a performance standpoint. Which is really faster?

1) $('a', '#myContainer');

2) $('#myContainer').find('a');

3) $('myContainer a');

#3 is obviously the slowest because it searchs all anchor tags (right-most) in the context of the document, then as descendants of #myContainer.

#1 has one method call and is in the context of #myContainer.

#2 has two method calls, the first where #myContainer is in the context of the document, it returns the jQuery set of elements (in this case one element if only 1 ID on the page with ‘myContainer’) which is then used as a context for the find() method.

It would appear that #1 is faster than #2. Thoughts?

By Joe McCann on Thursday, June 25, 2009 at 02:54 PM

@Brian

It don’t traversing all DOM — just the #myContainer element. It’s faster.

By Mushex Antaranian on Thursday, June 25, 2009 at 02:59 PM

@Joe, #2 is going to be the fastest. It will only be slightly faster than #1 though. This is because #1 has some overhead in being converted to #2 internally by jQuery. This is part of the reason for the article. You should just use the .find() syntax instead of passing a selector for the context argument. The context argument really serves a different purpose.

To see the performance difference paste the following chunks into Firebug on this blog post.

Test #1

console.profile();
for( var i=0; i<250; i++ ) {
    $('a', '#primary');
}
console.profileEnd();

Test #2

console.profile();
for( var i=0; i<250; i++ ) {
    $('#primary').find('a');
}
console.profileEnd();

Test #3

console.profile();
for( var i=0; i<250; i++ ) {
    $('#primary a');
}
console.profileEnd();

Test #4 And for fun, here is a test using the context “correctly”. It is slightly faster than 1, 2, and 3.

console.profile();
var context = $('#primary')[0];
for( var i=0; i<250; i++ ) {
    $('a', context);
}
console.profileEnd();

By Brandon Aaron on Thursday, June 25, 2009 at 03:51 PM

@joe, i think you missed an impt. point in the article mentioning that 1) and 2) are effectively the same query…

By sal on Thursday, June 25, 2009 at 04:02 PM

Is it correct to say that “the correct context” is an elment and not an array of elements?

By Michele Gargiulo on Thursday, June 25, 2009 at 04:02 PM

@Michele Basically that is correct. The context is a single element/node. Could also be the document node from an br for example.

By Brandon Aaron on Thursday, June 25, 2009 at 04:07 PM

So how about the difference between this:

var context = $('#myContainer')[0];
$('a', context);

and this:

$('a', $('#myContainer'));

? The first example seems to assume the variable the context is taken from is a collection, but that isn’t always the case, is it?

By Jimmy on Friday, June 26, 2009 at 06:56 AM

Isn’t the context of…

var context = $('#myContainer')[0];

a document?

If so, it will have to search against the whole document at this point. I haven’t tested though.

By KiT on Friday, June 26, 2009 at 07:28 AM

@Jimmy, Passing a jQuery collection as the context is basically the same as passing a selector. Internally jQuery converts it to this:

$( $('#myContainer') ).find('a');

It is more clearly written like this:

var $container = $('#myContainer');
$container.find('a');

In the first example of your comment, the context is pulled from a collection because jQuery is used to retrieve it and jQuery always returns a collection. I could have just as easily use document.getElementById like this:

var context = document.getElementById('myContainer');
$('a', context);

Not sure if that answers your question or not though.

By Brandon Aaron on Friday, June 26, 2009 at 02:10 PM

@Brandon

Ah, so passing $(‘#myContainer’)[0] in as the context is because it’s expecting the actual DOM element and not a jQuery object?

By Jimmy on Saturday, June 27, 2009 at 01:15 AM

hi!

I have set up a demo page with a simple <div id="primary"> and some <a>-tags inside.

at the end, I run the following code:

$(function() {
    var start = 0,
        end = 500;

    console.log("$('#primary a')");
    console.time('aaa');
    for (start = 0; start < end; start++)
    {
        $('#primary a');
    }
    console.timeEnd('aaa');

    console.log("$('a', '#primary')");
    console.time('bbb');
    for (start = 0; start < end; start++)
    {
        $('a', '#primary');
    }
    console.timeEnd('bbb');

    console.log("$('#primary').find('a')");
    console.time('ccc');
    for (start = 0; start < end; start++)
    {
        $('#primary').find('a');
    }
    console.timeEnd('ccc');

    console.log("$('a', context)");
    console.time('ddd');
    var context = $('#primary')[0];
    for (start = 0; start < end; start++)
    {
        $('a', context);
    }
    console.timeEnd('ddd');
});

the fastest one is the first(!) one… shouldn’t it be the last one????

By toby on Saturday, June 27, 2009 at 02:14 PM

@toby, As always when dealing with selector performance it depends on your DOM. Specifically how big it is and the structure. This is a classic case of pre-optimization. Don’t optimize until you actually need it.

Also, as a reminder because I don’t think the article made it very clear… the context argument is not there solely for performance. It serves a greater purpose such as the .live() binding in 1.3.3 and the ability to provide a different document where elements should be created. It can make your code perform better if you are having performance issues.

By Brandon Aaron on Saturday, June 27, 2009 at 03:54 PM

When a selector returns only 1 element, can jQuery not then change the context to that element? That would speed up most of these cases.

By Sean Catchpole on Thursday, July  2, 2009 at 04:32 PM

Just did some really interesting tests and discovered, as you mentioned, Brandon, that there are a lot of variables here - DOM size, structure, and, as it turns out, browser and type of selector.

In FF2 (which lacks a native getElementsByClassName), the fastest result on my page was not using context, and not even using find, but rather in using TWO finds!

The winner ended up being:

$('#myID').find('.myClass').find('a')

Over this:

$('#myID .myClass a')

Or this:

$('#myID').find('.myClass a')

Or this:

var context = $("#myID")[0];
$('.myClass a', context)

But that changed in FF3 (which has a native getElementsByClassName), where the winner was actually just the straight selector:

$('#myID .myClass a')

Pretty wild stuff. Thanks much for the head’s up! Without this article, I wouldn’t have even tested these things.

By Nathan Logan on Thursday, July  9, 2009 at 11:29 PM

@Brandon Is it 1:using the context “correctly” or is it 2:using the context correctly ? IF(2) THEN Why not just enforcing this in jQ 1.3.3 ? var context = $(‘#primary’)[0]; $(‘a’, context); // OK $(‘a’, “#primary”); // throw an exception

What is the point of allowing incorrect usage? Or is it “incorrect” ?

– DBJ

By DBJ on Thursday, August 20, 2009 at 12:45 PM

Hey Brandon,

I’ve posted a patch to make the context parameter behave more consistently: dev.jquery.com/ticket/5172 I’d like to have you opinion about it.

Regards,

Louis-Rémi

By lrbabe on Tuesday, September  8, 2009 at 05:31 PM

Brandon,

I appreciate this post. Somehow all this time using Firebug, I never noticed the profile() and profileEnd() methods.

I’ve been using Context incorrectly, as well – didn’t realize it expected a node rather than a jQuery collection.

However, I’d like to point something out. Your tests (with the profiling code, in your comment) were an unfair comparison. In test #2, you select the #primary element every time inside that loop, while in test #4 (where you show that using Context is slightly faster), you select the node outside of the loop.

I ran my own (near identical) tests, and at least in FF 3, using .find() is faster than specifying the context. I still prefer using context for its scope advantages, and chances are, the lookup on the parent element may have already have been done. But in an equal comparison, .find() is consistently faster (for me).

console.log('Select Header links using Find');
console.profile();
    var header = $('#header');
    for( var i = 0; i < 100; i++)
    {
        header.find('a');
    }
console.profileEnd();

console.log('Select Header links using Context');
console.profile();
    var headerNode = $('#header')[0];
    for( var i = 0; i < 100; i++)
    {
        $('a', headerNode);
    }
console.profileEnd();

By Richard Morgan on Monday, February 15, 2010 at 04:34 PM

@Richard Morgan That is definitely a valid point. I think I originally only “cached” the context because that is the “correct” behavior the article was trying to point out. That isn’t to say you shouldn’t be caching your other jQuery collections that will be used more than once! Thanks for sharing! :)

By Brandon Aaron on Thursday, February 18, 2010 at 04:21 PM

not sure if this has changed, but running the following tests on this page i get .find and using a jQuery object as the “context” to be significantly faster:

var maxReps = 1000,
    selectorParent = '#primary',
    selectorChild = 'a';

/*
console.profile('nocontext'+maxReps);  
for(i=maxReps;i>0;i--){
    $(selectorChild).toggleClass('test');
}
console.profileEnd('nocontext'+maxReps);  
*/

console.profile('find'+maxReps);  
for(i=maxReps;i>0;i--){
    $(selectorParent).find(selectorChild).toggleClass('test');
}
console.profileEnd('find'+maxReps);  

console.profile('text'+maxReps);  
for(i=maxReps;i>0;i--){
    $(selectorChild, selectorParent).toggleClass('test');
}
console.profileEnd('text'+maxReps);  

console.profile('dom_el'+maxReps);  
var $context = $(selectorParent);
for(i=maxReps;i>0;i--){
    $(selectorChild, $context).toggleClass('test');
}
console.profileEnd('dom_el'+maxReps);  

console.profile('context'+maxReps);  
var context = $(selectorParent)[0];
for(i=maxReps;i>0;i--){
    $(selectorChild, context).toggleClass('test');
}
console.profileEnd('context'+maxReps);

By Jeremy S on Wednesday, July 28, 2010 at 09:01 PM

New Comment
gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.