Jquery traversing and using selectors - javascript

I'm reading a book and they show a couple of examples of how to select elements on the DOM, but they advise to always use Jquery trasversing methods over the selectors, for example if you have a list inside a div instead of using
$("#myList > li")
You should use
$("#myList").children("li")
Most of the time I use the first over the latter, the author says the 2nd is prefered and more efficient but he does not address why, can anyone explain the reason behind this?

I think the difference in performance in that particular case comes down to this:
document.querySelectorAll('#myList > li');
// VS
document.getElementById('myList').children;
And the performance test here: http://jsperf.com/ae-d2-56-2a-e2-36-a3-d3-52-74
jQuery might check to see if it's a li given the selector but that's still going to be faster than querySelectorAll or Sizzle.

Related

Using CSS selectors instead of XPath locators when searching through siblings

Currently, I have the following page object fields:
this.filterTeamDropdown = $("filter-item-edit .dropdown button");
this.teams = this.filterTeamDropdown.all(by.xpath("following-sibling::ul//li[contains(#class, 'dropdown-list-item')]"));
Is there a way to replace the XPath locator for the teams field and have a CSS selector instead?
The motivation for it is coming from the Style Guide and the recommendation not to use XPaths.
From what I understand, it is impossible to have a CSS selector to go to the next sibling starting from current element in the context. But, are there any alternatives?
Saying NEVER to anything is silly. I strongly favor CSS selectors because locating element by id, CSS selector, just about anything... is faster than XPath. But... at the same time we're talking a few ms of difference.
There are some things that XPath can do that no other locator method can. One example that comes to mind is finding an element (other than A) by contained element text. Other than that I generally stick to CSS selectors when ID doesn't work.
I strongly dislike a lot of people's locator strategies on SO because XPath seems to be the goto way to find elements to the point where it's silly. I've seen people looking for nothing but an id and using XPath. I think part of it is the ease of which you can obtain an XPath, right click on element in inspector and copy XPath and paste in code. The problem with that, as I'm sure you know, is that sometimes (many times?) that results in a very brittle XPath but some/many people don't know any better.
All that said, I'll point you to the W3C CSS Selector reference and maybe you can find what you are looking for. There are some sibling combinators in there but I don't have your HTML so I don't know which, if any, of them would work.
https://www.w3.org/TR/selectors/#selectors
https://www.w3.org/TR/selectors/#adjacent-sibling-combinators
I just read some of the comments below your question and see that you already knew about the + combinator. Is there some reason you can't reuse your initial locator CSS with the XPath converted string? I don't know if this is even valid/usable but I've combined the two locators you provided in your code after converting the XPath to a CSS selector.
filter-item-edit .dropdown button + ul li.dropdown-list-item
There is an another, Protractor-specific way to solve it - use the locator() of the parent element and concatenate to make a child element selector:
this.filterTeamDropdown = $("filter-item-edit .dropdown button");
this.teams = this.filterTeamDropdown.$$(this.filterTeamDropdown.locator().value + " + ul li.dropdown-list-item")

jquery select all children after nth-child

I am working with jquery and creating show hide lists, I need hide all the child list items that follow the 6th child in the ul. Normally I would do this by setting the height and then changing the height on click of a click, but for this to work I need to add overflow:hidden to my css, and this is not an option.
How would I get all the child list elements that are greater than the 7th child?
Something like,
$("ul li:6n").domeSomething()
How would I get all the child list elements that are greater than the 7th child?
Select the element index = 7+
$("ul li:gt(6)").domeSomething()
:gt selector
The selector uses the zero base index:
Note that since JavaScript arrays use 0-based indexing, these selectors reflect that fact. This is why $('.myclass:gt(1)') selects elements after the second element in the document with the class myclass, rather than after the first. In contrast, :nth-child(n) uses 1-based indexing to conform to the CSS specification.
I wanted to write my answer only to discuss two previous answers: the answer from gdoron and the answer from neu-rah. If we would see on voting count one can see that the most reader find doron's answer better. I disagree and I try to explanation why.
The explanation you would find the documentation of :gt() Selector (see "Additional Notes:" here):
Because :gt() is a jQuery extension and not part of the CSS
specification, queries using :gt() cannot take advantage of the
performance boost provided by the native DOM querySelectorAll()
method. For better performance in modern browsers, use
$("your-pure-css-selector").slice(index) instead.
You can test the code with here or better here (with non-minimized code of jQuery 1.7.2). The code use just the statement $("ul li:gt(6)").css("color", "red");. You will better understand the problem if you start the demo in Developer Tools of Chrome with activated button "Pause on exceptions". You will see the following exception (it will be not the first one):
So you can see that the current implementation of jQuery try to use native querySelectorAll() of the web browser to get the best performance. After that the function $.makeArray will be used to make jQuery wrapper from the NodeList. In case of exception the line
return oldSizzle(query, context, extra, seed);
So you will have the best performance if you would use selectors supported by querySelectorAll(). For example
$("ul li").slice(7)
is better as
$("ul li:gt(6)")
I would you recommend to use more exact selectors whenever it's possible. For example if the parent <ul> element has id="menu1" then you can use
$("#menu1 >li").slice(7)
for the best results. It will help additionally in case of multiple <ul> elements on your page.
Someone can mention: the selector "ul li:gt(6)" works quickly enough. It's correct, but you should don't forget, that you use sometime selectors inside of loop or use it inside of functions which will be called inside of loops. So the difference between 10ms and 30ms can be much more clear if the execution time will be multiplicate to 100.
Moreover it somebody ask yourself the question how to implement some selection and get the answer, he/she will use the code pattern permanently. I think that it would be better to use the pattern which has performance advantage. Isn't so.
UPDATED: To clear the difference in performance of $("ul li:gt(6)"), $("ul li").slice(7) and $("#menu1 >li").slice(7) selectors I made additionally the demo. Everybody can test the difference in the web browser which he uses. You should additionally not forget, that in case of page having many other elements the id selector will work better.
On my computer the execution time of $("ul li").slice(7) and $("#menu1 >li").slice(7) are about the same (the page have very little elements) and is about 2.5-4.5 time better as the for $("ul li:gt(6)"). The results can depend on the number of li items, the number of elements on the page and many other things, but I hope that the test do clear shown that the usage of slice has performance advantage compared with the usage of :gt (exactly like we can read in the jqGrid documentation referenced before).
use slice to reduce a set
.slice(start[,end])
http://api.jquery.com/slice/
example (edited)
$("ul li").slice(6).doSomething(...)
Try this:
$('ul li:eq(6)').nextAll().domeSomething()

jQuery selector for finding the first element after a given element that matches a selector

Let's say I have this HTML:
<textarea>blah</textarea>
<br>
<div class="select_this">hello!</div>
How can I select the DIV with class "select_this" when I already have the textarea identified? I don't want to use a class selector on the entire document because I'm working with a large document and class lookups are slow in older browsers.
jQuery .next() doesn't seem to do the trick, closest() only looks up the DOM tree, and .nextUntil() qualifies on everything I need except for the "select_this" div. Any other options out there?
There are two ways to do it.
Use this if you only have a few siblings:
$('textarea').nextAll('div.select_this').first();
The downside of this is that it test every subsequent element to see if it matches the selector, even after it's found the first one. Use this if you have many, many siblings, to save on evaluation:
$('textarea').nextUntil('div.select_this').andSelf().last().next();
Note also that it's better to use the first and last methods, rather than their corresponding selectors (:first, :last), because browsers don't natively understand the selectors, which slows the expression down considerably.
Edited to incorporate andSelf per comment below.
You want nextAll:
jQuery(yourTextarea).nextAll('.select_this:first');

Performance in jQuery with selectors

i was wondering things...
If i need to get the content or append an click function to an div, as the structure of the selectors it's something like that:
$('body #content #sidebar .modalwindow #global-content')
i want to target #global-content, the final id of the selectors.
what its better?
Just target it as $('#global-content') and do what i wanna or give to it all the path?
$('#global-content') is the best selector to use, altough maybe the whole selector will be executed the same way (if jQuery starts from right to left, which I'm not sure it does). ID should be unique and getElementById() is the fastest native browser method, so $('#global-content') is the fastest possible selector.
Keep in mind also, that when you are searching for something exactly 1 level lower in the DOM tree, you can put > in the selector. Example:
$('body .content') is equialent to $('body').find('.content')
$('body > .content') is equialent to $('body').children('.content')
The second one is faster.
You can experiment and try out your selectors here
a similar question was asked in
jQuery Selectors, efficiency
the answer is that
$('#global-content')
is faster
if you know the id of your element and if your id is really unique (as it should be). It is faster to call directly the id >> $('#global-content').
Thus, it is interpreted by jquery to one of the fastest selector getElementById() instead of filtering the DOM.
Note: I know jquery 1.5 and higher (maybe even since 1.4) were optimized to select by id even if the jquery code was adding too much information but that's not the best way to rely on the framework to correct a bad coding

How to optimize $.find().first()?

I need to retrieve the first element.
I do that with this code...
$(element).find('.x').first();
As much as I understand, that code...
Retrieves all elements from element that matched .x,
Removes unneeded elements;
Is there any better way to do it? Like $.findOne() or something?
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
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.
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.
This should be better
$(element).find('.x:first');
Use :first selector:
$(element).find('.x:first')
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);
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
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.
You could combine the $(element) and .find() calls using a descendant selector; I'm unsure of the performance comparison:
$("#element .x").first().hide();
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.

Categories

Resources