Most efficient way to iterate over all DOM elements

JavascriptJqueryPerformanceDomOptimization

Javascript 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('*')).

The fiddle.

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:

http://jsfiddle.net/pQgwE/4/

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];
}

https://jsben.ch/FwPzW

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkevzettlerView Question on Stackoverflow
Solution 1 - JavascriptPaulView Answer on Stackoverflow
Solution 2 - JavascriptBriguy37View Answer on Stackoverflow
Solution 3 - JavascriptPetar SabevView Answer on Stackoverflow
Solution 4 - JavascriptCamilo MartinView Answer on Stackoverflow
Solution 5 - JavascriptJamie TreworgyView Answer on Stackoverflow
Solution 6 - JavascriptEylon SultanView Answer on Stackoverflow