5

I need to retrieve the first element.

I do that with this code...

$(element).find('.x').first();

As much as I understand, that code...

  1. Retrieves all elements from element that matched .x,
  2. Removes unneeded elements;

Is there any better way to do it? Like $.findOne() or something?

Don't Panic
  • 41,125
  • 10
  • 61
  • 80
daGrevis
  • 21,014
  • 37
  • 100
  • 139

10 Answers10

11

As per jQuery docs:

Because :first is a jQuery extension and not part of the CSS specification, queries using :first cannot take advantage of the performance boost provided by the native DOM querySelectorAll() method. To achieve the best performance when using :first to select elements, first select the elements using a pure CSS selector, then use .filter(":first").

So rewriting your selector to:

$(element).find('.x').filter(":first")

or (this one will give you direct descendants only and will be faster than .find, unless you're looking for nested elements too)

$(element).children('.x').filter(":first")

should give you better results.


Update After valuable inputs from kingjiv and patrick dw (see comments), it does seem that these two are faster than .filter(':first') contrary to what the doc claims.

$(element).find('.x').first();   // faster

$($(element).find('.x')[0]);     // fastest
Community
  • 1
  • 1
Mrchief
  • 75,126
  • 20
  • 142
  • 189
  • 1
    +1, but I would use `.first()` instead of `.filter(":first")`. I'm not sure why the documentation suggests `filter` instead, `.first` is faster. (`.first` makes a bit more sense for readability too, but that may just be preference) – James Montagne Aug 24 '11 at 17:02
  • @kingjiv: `filter` takes advantage of native `querySelectorAll()` which provides performance boost. `first` is the plain old style of selection where you first get all elements and the return the first one. So I guess the boost comes from single step execution in case of filter vs. 2 or more in case of `first`. – Mrchief Aug 24 '11 at 17:09
  • But unless my jsperf is flawed (modified someone else's I found on google) it isn't faster. Plus I'm not seeing the 2 steps, you already queried the elements, now `.first` just grabs the first one. http://jsperf.com/jquery-select-first/2 – James Montagne Aug 24 '11 at 17:12
  • 1
    @Mrchief: For the same reason jQuery can't take advantage of native code like `querySelectorAll()` when doing `.find('.x:first')`, it also can't when doing `.filter(':first')` (where I believe it tries to use a native `matchesSelector()`). It's because the selector isn't a valid CSS selector. And even though the selector is named `:first`, it is still run against *every* element in the set. The method `.first()` is just a wrapper for `.eq(0)`, which of course grabs the first element directly. – user113716 Aug 24 '11 at 17:18
  • Hmmm... that is weird. Seems like `querySelectorAll` is not getting used in the test or maybe its not as fast as documented or we're missing something subtle. – Mrchief Aug 24 '11 at 17:24
  • @patrick dw: After looking at the jsperf and jQuery source code, `.first()` does seem to be better then `filter`. Let me update my answer. – Mrchief Aug 24 '11 at 17:25
  • @Mrchief: Have a look at my answer regarding `querySelector`. It is way faster. – Felix Kling Aug 24 '11 at 17:35
4

If you want to have it real fast, you should use native browsers methods. Modern browsers support querySelector [docs]:

var $result;
if(element.querySelector) {
    $result = $(element.querySelector('.x'));
}
else {
    $result = $(element).find('.x').first();
}

The usage is a bit limited, as it would only work if element is a single element and if the selector is a valid CSS selector. You could make a plugin out of it. But then, if you consider all cases, like multiple elements etc., there is probably no advantage anymore.

So again, if you have a very specific use case, this might be useful, if not, stick with jQuery.

Update: Turns out, making a plugin is still faster: jsPerf benchmark

(function($) {
    $.fn.findOne = function(selector) {
        try {
            var element, i = 0, l = this.length;
            while(i < l && (element = this[i].querySelector(selector)) === null) {
                i++;
            }
            return $(element);
        }
        catch(e) {
            return this.find(selector).first();
        }
    };
}(jQuery));

How this works:

The plugin iterates over the selected DOM elements and calls querySelector on each of them. Once an element is found, the loop will terminate and return the found element. There are two reasons an exception could occur:

  • The browsers does not support querySelector
  • The selector is not a pure CSS selector

In both cases the plugin will fall back to use the normal jQuery method.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 1
    I wonder why the plugin version is faster than the direct call to `document.querySelector`. – user113716 Aug 24 '11 at 18:08
  • @patrick: I wondered about that too, I thought maybe the plugin is totally failing (not doing anything, but I think it works ;)). My guess is that it is a bit more expensive to start searching at the root of the document. And FWIW, there seems to be no difference in Chrome. – Felix Kling Aug 24 '11 at 18:11
  • Yeah, that's the only thing I could think of too. Surprising since the plugin needs to make multiple calls. Maybe `document.body.querySelector` would perform better. – user113716 Aug 24 '11 at 18:13
  • This is very nice! I also revised your test as `document.querySelector != $foo[0].querySelector`. If you look at [revised](http://jsperf.com/jquery-select-first/6) results, plugin version is way slower than direct access but still faster than jQuery ones. Wish I could upvote more! – Mrchief Aug 24 '11 at 20:49
  • @Mrchief: Thanks, although the adjustment is not necessarily better. The first `.foo` element does not have a `.selector` descendant. It don't understand why there is such a big difference between `document.querySelector....` and the plugin in FF compared to Chrome, but ok. Still interesting results :) – Felix Kling Aug 24 '11 at 20:59
  • That was going to be my next question - Are they doing the same thing? `document.querySelector` works on a bigger scope, hence is bound to be slower than an element scope. This is true for any jQuery selector as well. – Mrchief Aug 24 '11 at 21:03
  • @Mrchief: Yes it works in a bigger scope and only through that produces the same result. As I use the method on individual elements, they work on a smaller scope and of course if you compare only a single call, the smaller the scope, the faster the method. But I'm explicitly iterating over a set of nodes and this is still faster than making a single call to an internal method, and that is a bit surprising... – Felix Kling Aug 24 '11 at 21:11
2

As crazy as it seems, in every performance test I've seen, .first() has better performance than :first.

As most people are suggesting, it seems as though using $(element).find(".x:first") should have better performance. However, in reality .first is faster. I have not looked into the internals of jquery to figure out why.

http://jsperf.com/jquery-select-first

And apparently using [0] and then rewrapping in a jquery object is the fastest:

$($(element).find(".x")[0])

EDIT: See mrchief's answer for an explanation of why. Apparently they have now added it to the documentation.

James Montagne
  • 77,516
  • 14
  • 110
  • 130
0

This should be better

$(element).find('.x:first');
Dogbert
  • 212,659
  • 41
  • 396
  • 397
0

Use :first selector:

$(element).find('.x:first')
bjornd
  • 22,397
  • 4
  • 57
  • 73
0

It's better to write:

$('a:first');

What you're writing is "in 'element', find '.x' and return the first one". And that can be expressed like this

$('.x:first', element);
Ahmed Nuaman
  • 12,662
  • 15
  • 55
  • 87
0

how about using first-child pseudo class ? like

$(element).find('.x:first-child')

However it might generate issues if your structure is like

<div>
   <p></p>
</div>
<div>
   <p></p>
</div>

so actually it is not what you are looking for (if you mean general solution). Others mnetions :first and this seems to be the correct approach

mkk
  • 7,583
  • 7
  • 46
  • 62
0

Your bottleneck is really the .find(), which searches all the descendants instead of just the immediate children.

On top of that, you're searching for a class .x (which uses a jQuery custom search) instead of an ID or a tagname (which use native DOM methods).

I would use Mrchief's answer and then, if possible, fix those two bottlenecks to speed up your selector.

Blazemonger
  • 90,923
  • 26
  • 142
  • 180
0

That way is fine according to the jQuery documentation, or at least better than using :first selector.

You can try as alternatives .filter(":first") or get the first element using array accessor against the .find() result [0].

Also, instead of .find() you can change it to:

$('.x', element)

To narrow the search to .x elements inside element, intead of searching the whole document.

user113716
  • 318,772
  • 63
  • 451
  • 440
Einacio
  • 3,502
  • 1
  • 17
  • 21
0

You could combine the $(element) and .find() calls using a descendant selector; I'm unsure of the performance comparison:

$("#element .x").first().hide();
user113716
  • 318,772
  • 63
  • 451
  • 440
STW
  • 44,917
  • 17
  • 105
  • 161