Most efficient way to iterate over all DOM elements
JavascriptJqueryPerformanceDomOptimizationJavascript Problem Overview
Unfortunately I need to iterate over all the DOM elements of a page and I'm wondering what the most efficient technique is. I could probably benchmark these myself and might if I have the time but I'm hoping someone has already experienced this or has some options I hadn't considered.
Currently I'm using jQuery and doing this:
$('body *').each(function(){
var $this = $(this);
// do stuff
});
While it works, It seems to cause some lag on the client. It could also be tweaked with a more specific jQuery context like $('body', '*')
. It occurred to me that native Javascript is usually faster than jQuery and I found this:
var items = document.getElementsByTagName("*");
for (var i = 0; i < items.length; i++) {
// do stuff
}
I'm assuming the native option is faster. Wondering if there are other options I hadn't considered. Maybe a recursive option that iterates over child nodes in parallel.
Javascript Solutions
Solution 1 - Javascript
The Vanilla Javascript way you posted is the fastest. It will be faster than the jQuery solution you posted (See my comment on the question). If you're not removing or adding anything to the DOM in your loop and order of traversal doesn't matter, you can also speed it up ever so slightly by iterating in reverse:
var items = startElem.getElementsByTagName("*");
for (var i = items.length; i--;) {
//do stuff
}
Edit: check this benchmark to see how much time you can save by using the native code: http://jsben.ch/#/Ro9H6
Solution 2 - Javascript
UPDATE:
Don't use $('body *')
to iterate over the elements. It will be much quicker to use $('*')
if you go for the JQuery method (see comments for details).
Plain ol' JavaScript is much faster, relatively speaking.
Using a test fiddle, I get about 30ms to process 13000 elements with JQuery, and 8ms to process 23000 elements using JavaScript (both tested on Chrome):
JQuery: 433 elements/ms
JavaScript: 2875 elements/ms
Difference: 664% in favor of plain ol' JavaScript
Note: Unless you have an incredibly large amount of elements on your page, this isn't going to make much of a difference. Also, you probably should time the logic in your loop, as that might be the limiting factor in all this.
Update:
Here is the updated results when considering much more elements (about 6500 per loop), I get about 648000 elements in 1500ms with JQuery, and 658000 elements in 170ms with JavaScript. (both tested on Chrome):
JQuery: 432 elements/ms
JavaScript: 3870 elements/ms
Difference: 895% in favor of plain ol' JavaScript
Looks like JavaScript sped up while JQuery stayed about the same.
Solution 3 - Javascript
It's not a good idea generally but this should work:
function walkDOM(main) {
var arr = [];
var loop = function(main) {
do {
arr.push(main);
if(main.hasChildNodes())
loop(main.firstChild);
}
while (main = main.nextSibling);
}
loop(main);
return arr;
}
walkDOM(document.body);
Not including textnodes:
function walkDOM(main) {
var arr = [];
var loop = function(main) {
do {
if(main.nodeType == 1)
arr.push(main);
if(main.hasChildNodes())
loop(main.firstChild);
}
while (main = main.nextSibling);
}
loop(main);
return arr;
}
Edited!
Solution 4 - Javascript
The fastest way seems to be document.all
(note that it's a property, not a method).
I've modified the fiddle of Briguy's answer to log these instead of jQuery, and it's consistently faster (than document.getElementsByTagName('*')
).
Solution 5 - Javascript
This is a solution to the problem as described in the comments (though not the actual question). I think it would be much faster the use elementFromPoint
to test the area where you want to put your fixed-position element, and only worry about elements in that area. An example is here:
Basically, just set some minimum possible size of an element you're looking for, and scan the entire area that your new fixed position element wants to occupy. Build a list of unique elements found there, and only worry about checking the style of those elements.
Note that this technique assumes that the element you're looking for has the highest z-index (which seems a reasonable assumption for fixed position). If this is not good enough, then this could be adjusted to hide (or assign a minimum z-index) to each element after it's been discovered and test the point again, until nothing more is found (to be sure), and then restore them afterwards. This ought to happen so fast as to be imperceptible.
HTML:
<div style="position:fixed; left: 10px; top: 10px; background-color: #000000;
color: #FF0000;">I Am Fixed</div>
<div id="floater">OccupyJSFiddle!<br>for two lines</div>
JS:
var w = $(window).width(), h=$(window).height(),
minWidth=10,
minHeight=10, x,y;
var newFloat = $('#floater'),
maxHeight = newFloat.height(),
el,
uniqueEls=[],
i;
for (x=0;x<w;x+=minWidth) {
for (y=0;y<h&& y<maxHeight;y+=minHeight) {
el = document.elementFromPoint(x,y);
if (el && $.inArray(el,uniqueEls)<0) {
uniqueEls.push(el);
}
}
}
// just for the fiddle so you can see the position of the elements
// before anything's done
// alert("click OK to move the floater into position.");
for (i=0;i<uniqueEls.length;i++) {
el = $(uniqueEls[i]);
if (el.css("position")==="fixed") {
el.css("top",maxHeight+1);
}
}
newFloat.css({'position': 'fixed',
'top': 0,
'left': 0});
Solution 6 - Javascript
Most efficient:
const allDom = document.all || document.querySelectorAll("*");
const len = allDom.length;
for(let i=0; i<len; i++){
let a = allDom[i];
}