Perform a task on every text node within a document that is not a direct descendant of an element with a specified tag name

I experimented with various techniques to achieve this task, but unfortunately, they all seem to be running quite slowly.

The array noGlobalTags is comprised of three tags.

First Approach:

var textNodeWalker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, function(node){return (noGlobalTags.indexOf(node.tagName)==-1)? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;}, false);
var cn;
while(cn = textNodeWalker.nextNode()){
  textNodeEmoteParser(cn);
}
return;

Second Approach:

function getTextNodes(node) {
    if(node.nodeType == 3) {
        textNodeEmoteParser(node);
    } else {
        for(var i = 0, len = node.childNodes.length; i < len; ++i) {
            if(noGlobalTags.indexOf(node.childNodes[i].tagName)!=-1) {
                continue;
            }
            getTextNodes(node.childNodes[i]);
        }
    }
}

getTextNodes(node);

I initially expected the first method to be faster, however surprisingly, it took twice as long as the second method. Is there a mistake in my approach? If not, is there a way to optimize it further for increased speed compared to the second method?

Answer №1

When it comes to JavaScript, inline code (like in the 2nd example) is typically faster than using callbacks (as seen in the 1st example). This is because there is a significant overhead for a function call in JavaScript.

Therefore, the treeWalker method with callbacks may not be as fast as directly coded methods like in your 2nd example.

A similar situation applies when comparing a direct iteration of an array using a 'for' loop versus using the '.forEach()' method for the same array. Callback functions tend to slow down execution.

Although code with callback functions can be more versatile and sometimes shorter, it may not necessarily execute faster.

This lack of surprising speed improvement in callback functions is due to the process involved in calling a function in JavaScript. It requires name lookup, creating a new function context, passing parameters, creating local variables, executing the code, and cleaning up afterwards among other steps. The inline code version bypasses many of these steps.

To optimize both approaches, you can place tags in an object rather than an array:

var noGlobalTags = {"LI": true, "UL": true};

You can then check if a specific tag is in that list using code like:

noGlobalTags[node.tagName]                    // first example

or

if (noGlobalTags[node.childNodes[i].tagName])   // second example

This type of lookup should be much faster than the array search currently being used.

Below is a non-recursive version that also utilizes the quicker noGlobalTags lookup:

function getTextNodes(top) {
    var queue = [], index, item, node;

    queue.unshift({nodes: [top], pos: 0});

    while (queue.length) {
        item = queue[0];
        index = item.pos++;
        
        if (index < item.nodes.length) {
            node = item.nodes[index];
            
            if (node.nodeType == 3) {
                textNodeEmoteParser(node);
            } else {
                
                if (node.childNodes.length && !noGlobalTags[node.tagName]) {
                    queue.unshift({nodes: node.childNodes, pos: 0});
                }
            }
        } else {
            queue.shift();
        }
    }
}

Update: this method is nearly 100 times faster. Another more efficient method that walks the tree directly has been discovered:

function customIterativeTreeWalker(root) {
    var node = root.firstChild;
    
    while (node != null) {
        if (node.nodeType == 3) {
            textNodeEmoteParser(node);
        }
        
        if (node.hasChildNodes()) {
            if (node.tagName && noGlobalTags[node.tagName]) {
                node = node.nextSibling;
            } else {
                node = node.firstChild;
            }
        } else {
            while (node.nextSibling == null) {
                node = node.parentNode;
                if (node == root) {
                    return;
                }
            }
            node = node.nextSibling;
        }
    }
}

Link to JSPerf comparison: http://jsperf.com/get-text-nodes-non-recursive/3

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Displaying images in Dash Python by utilizing external Javascript

Dear audience, I am looking to incorporate an image on a Python Dash webpage that is created by JavaScript code. For more details, you can refer to the documentation here: . To include this script in a static HTML page, one would typically add the followi ...

The root element is being rendered as null in a scope without the DOM

My goal is to render null at the root in order to create a full tree that renders null, while still taking advantage of React's component structure and lifecycle methods. If you're interested in learning more about the "returning null concept," ...

Unable to retrieve the parent element of an event target in AngularJS when using Internet Explorer 11

function determineClosestAncestor(startElement, callback) { const parent = startElement.parentElement; if (!parent) return undefined; return callback(parent) ? parent : determineClosestAncestor(parent, callback); }; $document.on('click', e ...

Ways to control the display of pagination pages in a React application

I am working on a web application using the MERN stack. The pagination feature has been implemented in React and Bootstrap. https://i.sstatic.net/ZqbLm.jpg https://i.sstatic.net/iAOIL.jpg WHAT AM I LOOKING FOR? I currently have a small data set, so the pa ...

AngularJS: Issue with Select Click Trigger Not Fired

I am puzzled as to why this code isn't working on Chrome but works perfectly fine on FireFox SelectModel selectModel: [ { value: "asc", htmlText : "A-Z", selected: "" }, ...

Removing an event from Fullcalendar

With the help of a post on StackOverflow, I managed to modify my select: method to prevent users from adding an event on a date before NOW. However, there is a drawback. When a user clicks on an empty time slot and the system displays an alert message, th ...

Using Node.JS, Sequelize, and Moment.JS to format dates

Seeking help on formatting a date loaded from Sequelize in my database. I'm working on a blog and need to display the creation date of an article. Here's my route: app.get("/", (req,res) =>{ Article.findAll({ order:[ [ ...

Adding a tooltip with a date format to a Highchart graph

Hey everyone, I'm currently working with a Highchart and I want to customize the tooltip value in a specific format. My categories and series are structured as follows: {"Categories":["2015-11-09","2015-11-08""2015-11-15"],"Series":[2,0,2]} Current ...

Creating a real-time text field update feature for a form using Google Script

One of my clients is dealing with a large number of contacts and to streamline the process, I created a form with a scrolling list for contact selection. However, the list has become too long to navigate easily. Is there a solution that would allow the c ...

Exploring the process of retrieving JSON from an API using plain JavaScript within the Protractor framework

I'm attempting to retrieve the distance between two locations using the TomTom API. Unfortunately, I am facing an issue with Protractor: When trying to use the *fetch function - I receive an error stating "fetch is not defined" and it advises me to ...

Tips for creating a scrollable smiley input field

I have a chat system where I am incorporating smileys from a predefined list. QUERY I am looking to create a scrolling feature for the smileys similar to how it is implemented on this particular website. The twist I want to add is to have this functiona ...

Split a string containing integer and decimal values into an array

I encountered an issue when converting a string to an array: var data = "[[1, 2, 3,], [3, 2, 1]]"; var array = JSON.parse(data.replace(/,\s*]/g, ']')); This problem arises when handling floating point numbers in the input: var data = "[[2 ...

Create a smooth animation of scaling horizontally in Framer Motion while ensuring that the scale of the children elements

Framer motion 4 has deprecated the use of useInvertedScale(). The new recommendation is to use the layout prop, but it doesn't seem to achieve the same effect for me. I'm attempting to scaleX a parent div without affecting the scale of its childr ...

Validation has failed due to a missing title field in MongoDB

I keep getting the error message ValidatorError: Path `title` is required and my data is not getting stored in the database. What could be causing this issue? const mongoose=require('mongoose'); const Articleschema= new mongoose.Schema({ ...

Display Firebase information in JSON format on the browser console

Can someone assist me in displaying my Firebase database data as JSON in the browser console? I am looking to load only Product data. I came across a code snippet on how to push data from this tutorial. Can anyone help me understand how the function work ...

The ins and outs of implementing i18n on an Angular component library

My custom component library is not functioning properly with i18n within my module // To enable Ahead-of-Time compilation, a function must be exported for factories export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(ht ...

Injecting Javascript before page code in Chrome Extension Content Script allows for manipulation of webpage elements

I am currently working on developing a Chrome extension that involves injecting a script into a webpage before any other scripts present. This is crucial for my project as I am utilizing the xhook library to intercept XHR requests, which necessitates overw ...

"Exploring the mechanics of this code: delving into the Buffer()

On a particular website, there is a task where you can solve puzzles and then compare your solution with others. I came across one solution that seems very concise and I'm having trouble understanding it. The task involves determining whether two cel ...

Tips for troubleshooting my website?

After making some updates to my website, I noticed that an ajax script is no longer functioning properly. Since I hired someone else to write the code, I'm not sure how to troubleshoot it. Initially, this is what it should look like: When you select ...

Difficulty navigating through nested arrays

One instance involves the use of the fetch API to retrieve data from the NY Times API. Within the response, there is an object titled "Multimedia" containing images. I've devised a template literal to extract the required information but for some reas ...