Tree Walker

Take two :slightly_smiling_face:

I’ve been digging into document.createTreeWalker() lately…
It’s a little bit syntax heavy but pretty powerfull.

The pro’s are:

  • 2X speed on document.querySelectorAll(*)
  • Within 5% ± of querySelectorAll(elements)
  • Way more capable with filter options
  • Can query any node type inc text, comment, attr ect
    IF… the treeWalker is used with a loop and not a callback

The basic syntax is:
let walker = document.createTreeWalker( element, NodeFilter.SHOW_ELEMENT, filterCallback , false);

This will run the walker looking for node type element with a callBack filter function.

The performance on this is 10X slower than querySelectorAll() due to the callback function being invoked every node…

However if you put your filter in the loop the speed is the same but you have more options and you can also walk over Child/ Sibling and Parent nodes before continuing the loop…

There’s two main ways to do this (make walker filter param null):

with a while loop (somewhat costly if called repeatedly) e.g.

let iter = 0;
let node = walker.nextNode();
while(node){
if(node.tagName === 'DIV'){ node.className = "classy";
node = walker.nextNode(); iter ++
}

Or with a for loop (more performant if less than 1000) e.g

        for(let node = walker.nextNode(), i = 0; // assign loop vars
            node; // evaluate if (node == null) break
            node = walker.nextNode() // iterate loop onto the next node
            ){
                if(node.tagName === 'DIV'){ node.className == 'classy"; i++;
                    }          
        }

This is how I’ve been using it but the options are pretty much limitless…


const diffNodes = function(element, objects, props){
    let walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT, null, false);
        for(let node = walker.nextNode(), i = 0;
            node;
            node = walker.nextNode()){
                if(node.tagName === objects[i].tag){
                    if(objects[i].text) node.insertBefore(document.createTextNode(objects[i].text), node.childNodes[0])
                    for(let prop in props ){
                        node[prop] = objects[prop]
                    }
                    i++;
                }
        }
    }

This is really cool. I don’t think I’ve used createTreeWalker ever outside of maybe you mentioning this a while ago :slight_smile:

It looks it would be pretty handy for web scraping…

I’ve got a few large volume professional JS books that cover ES6+ inside out and they don’t have much on it. It’s like the authors have never used in before either.

I had a use case where I wanted to query elements as well as text nodes and XPath looked like a horrible option…

Once I stumbled on it, I realized how powerful it is…

It’s pretty handy with custom-elements to query all elementChildren.
It flattens the children and iterates 50% faster than querySelectorAll(*) which is good when you do that on a heap of custom-elements.

It’s also very handy for HTML tables. I just wish Safari would sort it out and implement customized built in elements so we could extend <tr> to <table-row>

1 Like

Just FYI:

  • If your using a treeWalker() and you intend to replace/ remove nodes go with a nodeIterator() because it flattens the tree and will iterate where treeWalker() will stop if the branch is removed/ replaced.

  • If you replace/ append nodes using a nodeIterator() and then intend to iterate over those you will need to set node = iterator.referenceNode (last node before replace/ append) and then node = iterator.nextNode()