what is faster - $().closest() or native while loop? - javascript

I have my own implementation of dropdown list. I am storing all list identifiers in the global array and on window click event I am iterating through this array and deciding which list I have to hide.
I need to check if element has ancestor with .active class.
jQuery version:
for (var i = 0; i < window.dropdowns.length; i++) {
var e = window.dropdowns[i];
if ($(event.target).closest('.active').length == 0) {
e.hideList();
}
}
Pure javascript version:
for (var i = 0; i < window.dropdowns.length; i++) {
var e = window.dropdowns[i];
var parent = event.target.parentElement;
while (parent.tagName != 'BODY') {
if (parent.className.indexOf('active') > 0) {
e.hideList();
break;
}
parent = parent.parentElement;
}
}
So, what version of this will be faster? And how performance depends on number of elements on the page?

If you just want to know what's fastest, write a simple test to find out. If you want to know why...
jQuery is going to do more work than you need, for example, you are only testing tagName and className.
Having said that, your code could give you false positives because className.indexOf('active') will return > -1 if the element has a class of notactive. Use classList instead.
Finally, if you are already using jQuery, you should use it since we just showed you that code we write ourselves can be buggy, and performance is not likely to matter in this case.
Remember, premature optimization is the root of a lot of spaghetti code.

If you were interested in knowing hard numbers, you could turn your loops into functions and use console.time and console.timeEnd on them. Something like this:
//First while loop
console.time(firstWhileLoopInFunction)
console.timeEnd(firstWhileLoopInFunction)
//Second while loop
console.time(secondWhileLoopInFunction)
console.timeEnd(secondWhileLoopInFunction)

Related

Get all elements with `position:fixed` in an HTML page?

Reason for doing that: I'm debugging css of my webpage.. some elements appeared and they're not supposed to appear. I suspect it is the issue with element positioning.. therefore I want to find these positioned element and check one by one.
This one is using jQuery. I hope you are find with it.
var find = $('*').filter(function () {
return $(this).css('position') == 'fixed';
});
I think this one works using a pure javascript:
var elems = document.body.getElementsByTagName("*");
var len = elems.length
for (var i=0;i<len;i++) {
if (window.getComputedStyle(elems[i],null).getPropertyValue('position') == 'fixed') {
console.log(elems[i])
}
}
Here is an ES6 version that gives you an array of these elements for further processing:
let fixedElements = [...document.body.getElementsByTagName("*")].filter(
x => getComputedStyle(x, null).getPropertyValue("position") === "fixed"
);
document.querySelector('*[style="position:fixed"]')
The * item specifies all tag names. The [] indicate that you're looking for an attribute. You want your style attribute to have position:fixed.
If you aren't using jQuery, this is probably going to be your simplest option.
Warnings that apply to all answers:
This is a slow operation. On a large-enough page, this operation can take 100ms or more, which is a lot for a single operation. You shouldn't need this unless you're developing a browser extension.
Now sticky elements can act as fixed elements in some cases
Having said that, here's the shortest and most efficient version to do this:
const fixed = [].filter.call(document.all, e => getComputedStyle(e).position == 'fixed');
Here's a version that includes sticky elements, but they're not exactly equivalent, it depends on what you're looking for:
const all = [].filter.call(
document.all,
e => ['fixed', 'sticky'].includes(getComputedStyle(e).position)
);
If you're feeling modern, replace document.all with document.querySelectorAll('*'), but the former will likely work forever.
Try this:
var elements = $('*').filter(function () {
return this.style.position == 'fixed';
});
It will give you all elements having position fixed.

How to turn jQuery each() into regular javascript loop

A couple questions:
Is a regular javascript loop (to loop through a series of elements) faster/more efficient than using jQuery each() ??
If so, what is the best way to write the following code as a regular javascript loop?
$('div').each(function(){ //... })
Yes, removing the each() will give you slightly better performance. This is how you could write a for loop for a list of elements.
var divs = $('div');
for(var i = 0; i < divs.length; i++){
var thisDiv = divs[i]; // element
var $thisDiv = $(divs[i]); // jquery object
// do stuff
}
var divs = document.getElementsByTagName('div'),
l = divs.length, i, cur;
for(i=0; i<l; i++) {
cur = divs[i];
// do stuff with cur here
}
Please continue on your path to removing jQuery in the name of efficiency. This code is approximately fifty times faster than the jQuery equivalent.
To answer your second question, due to the first already being answered;
I was also interested in this, and I decided to benchmark the two to find the difference using Gabe's example. The answer is, in circumstances of wanting a jQuery object as the final result:
They perform exactly the same.
http://jsperf.com/jquery-each-vs-native-selectors
Firefox actually finds the jQuery version faster, apparently.
Instead of using jquery .each()
$('div').each(function(){ //... })
You can use document.querySelectorAll(), then convert the HTMLCollection into a JavaScript array. Then you can map over the array of elements.
const elems = document.querySelectorAll('.my-elements')
const $elems = [].slice.call(elems)
$elems.map( (elem) => { console.log(elem) })

How to get the number of DOM elements used in a web page

Using jQuery I can easily get the number of DOM elements used by a web page:
$('*').length;
But not all web sites are using jQuery.
So my question is: How do I get the number of DOM elements used in a web page using pure JavaScript and js console.
Assuming you mean "HTMLElementNodes" as opposed to "All nodes" (which would include such things as text nodes and also be skipped by your jQuery example) then:
document.getElementsByTagName('*').length
This still requires the use of DOM though. Pure JavaScript can't interact with an HTML document other than as a string of text.
It's quite simple really:
document.getElementsByTagName('*').length
If you can ignore older browsers, you could also preserve some of the jQuery-like syntax with:
var $;
$ = document.querySelectorAll.bind(document);
$('*').length;
This question is the top result on google for "js count all dom nodes" but it's 2021 now and we have ShadowDOM so none of these previous answers will actually give an accurate result.
Better to use this function which is based on the code used by Lighthouse to calculate the true DOM size.
function countNodes(element = document.body) {
let count = 0; let child = element.firstElementChild;
while (child) { count += countNodes(child);
if (child.shadowRoot) { count += countNodes(child.shadowRoot); }
child = child.nextElementSibling; count++;
} return count;
}
Using a recursive function countChildrenNumber:
function countChildrenNumber(el) {
let result = 0
if (el.children && el.children.length > 0) {
result = result + el.children.length
for (let i = 0; i < el.children.length; i++) {
result = result + countChildrenNumber(el.children[i])
}
}
return result
}
then call it by passing document as the parameter
countChildrenNumber(document)
The main answer doesn't really count everything (I think shadow DOM would be excluded)
Using snippet below works better for me:
$$('*').length

javascript .children not working in Camino Browser

This is an odd one, for whatever reason, getting the children of an element doens't work in Camino browser. Works in all other browsers. Anyone know how to fix this? Google is no help :(
var site_result_content = document.getElementById(content_id);
site_child_nodes = site_result_content.children;
alert('started');
for(i=0;i<site_child_nodes.length;i++) {
alert('cycle1');
document.getElementById(site_child_nodes[i].id).className = 'tab_content';
ShowHide(site_child_nodes[i].id,'hidden');
}
In this case, the started alert is called, but the cycle1 isn't.
Use childNodes instead. children started out as a proprietary property that in IE, whereas childNodes is in the W3C DOM spec and is supported by every major browser released in the last decade. The difference is that children contains only elements whereas childNodes contains of all types, in particular text nodes and comment nodes.
I've optimized your code below. You should declare all your variables with var, including those used in loops such as i. Also, document.getElementById(site_child_nodes[i].id) is unnecessary: it will fail if the element has no ID and is exactly the same as site_child_nodes[i] otherwise.
var site_result_content = document.getElementById(content_id);
var site_child_nodes = site_result_content.childNodes;
alert('started');
for (var i = 0, len = site_child_nodes.length; i < len; ++i) {
if (site_child_nodes[i].nodeType == 1) {
alert('cycle1');
site_child_nodes[i].className = 'tab_content';
ShowHide(site_child_nodes[i].id, 'hidden');
}
}
I'd hazard a guess that it's not been implemented yet (it was only implemented in Firefox 3.5). You can use childNodes instead, which returns a list of nodes (rather than just elements). Then check nodeType to make sure it's an element.
var site_result_content = document.getElementById(content_id);
site_child_nodes = site_result_content.childNodes;
alert('started');
for(i=0;i<site_child_nodes.length;i++) {
// Check this is actually an element node
if (site_child_nodes[i].nodeType != 1)
continue;
alert('cycle1');
document.getElementById(site_child_nodes[i].id).className = 'tab_content';
ShowHide(site_child_nodes[i].id,'hidden');
}

how to using "For" instead of "each" function in jquery

Today i'm very stack with a Work and jQ. I was get a morning for it but i can't resolve it :(.
My Work here:
<div class="container">
<p class="test">a</p>
<div>
<p class="test">a</p>
</div>
</div>
In normal, i can using jQ with each function for select all <p class="test">a</p> EX:
$(".test").each(function() {
$(this).text('a');
});
But i hear everyone talk that, for function get a less timeload than each function. Now i want using for instead of each.. but i don't know how to write code jQ in this case.
Somebody can help me!. thankyou!
I wouldn't worry about it unless you were iterating through hundreds of them.
for loop is usually used with normal DOM (aka without jQuery) traversing, like...
var elements = document.getElementById('something').getElementsByTagName('a');
var elementsLength = elements.length;
for (var i = 0; i < elementsLength; i++) {
elements[i].style.color = 'red';
}
Caching of elementsLength is a good idea so it is not calculated every iteration. Thanks to CMS for this suggestion in the comments.
Just adapt that for your jQuery object if you wanted to do it with jQuery.
Replace elements variable with your jQuery collection, like $('#something a'). I think you may need to rewrap the object if you need to do any more jQuery stuff with it.
One thing to watch out for is that using an ordinal accessor on the result of a jQuery selection will return a native DomElement. If you want to use jQuery methods on them, you have to re-wrap them:
var testElements = $('.test');
for (var i = 0; i < testElements.length; i++) {
// Using $() to re-wrap the element.
$(testElements[i]).text('a');
}
I'd second what others have said though. Unless you're dealing with many elements, this is premature optimization. Re-wrapping the elements to use the .text() method may even bring it back to no gain at all.
have you tried the obvious solution?
var nodes = $(".test");
for(var i = 0; i < nodes.length; i++)
{
var node = nodes[i];
}
This article shows that each() has no significant performance penalty until you get into the hundreds of thousands of looped-over items.
Another alternative:
for (var i = 0; i < $('.test').length; i++){
var element = $('.test').eq(i);
}

Categories