tags:

views:

70

answers:

3

From what I understand adding .first() or :first to a query doesn't stop DOM search after the first match, it just tells jQuery to take 1st element out of matched collection.

If it is true, then is there a way to stop DOM search after the first match? For example if I know that the result of this query will always be a single element, how to tell jQuery to not waste time searching further?

+3  A: 

:first matches only a single element. its equivalent to :eq(0).

.first() reduces the set of matched elements to the first in the set.

so using :first would seem like its what you need, but on looking closer they're both using selectors first to find a set and then going on to use the first.

There's some comments and discussion here and here that seems to indicate that :first() doesn't stop at the first element.

References:

http://api.jquery.com/first-selector/

http://api.jquery.com/first/

Moin Zaman
Both the `:first` selector and the `.first()` method build up a list of all matching elements, and only then take the first result, discarding the rest. The OP is looking for an optimisation to avoid gathering the superfluous elements in the first place.
bobince
@bobince: thanks mate, made me do more research and try and find out whats really behind it.
Moin Zaman
`:first` and `.first()` may behave the same, but internally they are different. `:first` never makes a call to `.eq()`
BrunoLM
A: 

I'm not an expert on jQuery's innards, but my understanding is that the order in which jQuery may find things isn't necessarily the order they appear in the DOM. In order to ensure that you do get the item that is first in the DOM, jQuery gets everything and sorts the result so that the first element is returned.

As you've noticed, this is a performance issue. However, I don't believe you can turn this behaviour off.

What you can do is make your queries more specific, so that less gets selected and there are less elements for jQuery to sort in order to find the first one. For example, $('li:first') isn't very specific and will be slow. $('#myList>li:first') will in theory be a lot faster (though I've not benchmarked it to check) because a) it will only search in a single list, and b) sublists will also be excluded.

If you will be using all the items in a selection for something at some point, but only need the first one at a particular point, then it makes sense to run the selector to get all your elements and save it to a var, then use .first() to get the first element when you need it.

var elems = $('li'), firstElem = elems.first ();

Now elems will hold the full set, and firstElem will hold just the first element.

Of course, you could also just give the first element in each group you intend to select a specific class, but this might be considered an inelegant solution.

Gordon
+4  A: 

As far as I know there's no way to do this in jQuery at the moment. The best you can do is $(selector).first(), where selector is a standard CSS selector not including any Sizzle-specific selectors (so don't use :first). In this case jQuery uses the fast DOM querySelectorAll method in modern browsers.

You can get the optimisation manually by using the DOM querySelector method which is built to select only the first match:

$.getFirst= function(selector) {
    if ('querySelector' in document)
        return $(document.querySelector(selector));
    else
        return $(selector).first();
}

however the amount you'll save by doing this is much smaller than the amount you save by making sure querySelectorAll can be used.

bobince
What if `querySelector` also works like `first` - gets first element out of a collection? :) Strange that jquery doesn't use `querySelector` anywhere, wouldn't it be clear improvement for `:first` selector implementation?
serg
Yeah, on some browsers `querySelector` might indeed be implemented using `querySelectorAll`! I haven't really stress-tested this, but I wouldn't expect much of a difference either way. `querySelectorAll` is already way fast compared to all the other JS selector engines that came before it.
bobince
jQuery's use of `querySelectorAll` is a short-circuiting layer on top of Sizzle, a bit like the above function. One could maybe add an optimisation to detect `:first` at the end of the selector and switch on that (must test to see if this is really worthwhile!), but the general case where `:first` might be in the middle of a selector isn't readily doable (due to various problems with scoped selectors which already often stop jQuery using `querySelectorAll`).
bobince