2

I'm using the function replaceStr() to replace some strings in the body tag. If the html page is small, the replacing is not noticeable. But if the html page is bigger and more complex you notice it. The replacing is blocking the browser. My question, how is it possible to make the replacing non-blocking? The replacing is not critical to the page, so it can happen in the background, when the browser is not busy. I tried to use async and await, but I think the replaceWith() function can't handle Promises and that's why it's not working with async/await. But how can you do it then?

function replaceStr(myStrArr) {
  const container = $('body :not(script)');
  myStrArr.map((mystr) => {
    container
      .contents()
      .filter((_, i) => {
        return i.nodeType === 3 && i.nodeValue.match(mystr.reg);
      })
      .replaceWith(function () {
        return this.nodeValue.replace(mystr.reg, mystr.newStr);
      });
  });
}

Thank you for your help.

David Lee
  • 275
  • 4
  • 13
Oliver
  • 79
  • 6

1 Answers1

2

Your current implementation has a few spots where it could be optimized before going the async route. For example you could get rid of the jQuery dependency. It doesn't help much in your case but adds overhead.

Then currently you're mapping over your replacements and for each over all candidate nodes, replacing the nodeValue each time. This possibly triggers a repaint every time.

Instead you could use a TreeWalker to quickly iterate over the relevant nodes, and only update the nodeValues once.


In my tests, the following runs roughly 16 times faster, than your current code. Maybe that's already enough?

function replaceStr_upd(replacements) {
    // create a tree walker for all text nodes
    const it = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
        // but skip SCRIPTs
        acceptNode: node => node.parentNode.nodeName === 'SCRIPT'
                            ? NodeFilter.FILTER_REJECT
                            : NodeFilter.FILTER_ACCEPT
    });

    // a helper function
    const applyReplacements = initial => replacements.reduce((text, r) => text.replace(r.reg, r.newStr), initial);

    // iterate over all text node candidates
    while (it.nextNode()) {
        // but only update once per node:
        it.currentNode.nodeValue = applyReplacements(it.currentNode.nodeValue);
    }
}
Yoshi
  • 51,516
  • 13
  • 81
  • 100
  • Holy Moses Yoshi, tank you very much. The code... my head just exploded. OK, I have a lot to read and understand, very impressive. And what is if I need to exclude other stuff like ":not(a):not(.myClass)"? Can I then simply add an or like " node.parentNode.nodeName === 'SCRIPT' || node.parentNode.nodeName === '.myClass' "? – Oliver Jul 20 '20 at 13:30
  • Ohh and a last question, does this solution also work if I want to replace a word with some html code, like a someWord? Or is this only capable of replacing pure text? – Oliver Jul 20 '20 at 13:34
  • @Oliver First I'd suggest to thoroughly read the documention on tree walkers, but yes you would only need to update the `acceptNode` method to appropriately return `reject/accept` depending on your needs. Though it might make sense to extract the whole creation logic to a helper method if it get's too complex. Regarding your second question: As it stands you can only pass in *plain text*, because setting the `nodeValue` on a text node acts as if you'd set the `textContent` on any other node. But you could just as well simply set the `innerHTML` on parent node. – Yoshi Jul 20 '20 at 18:13
  • a very nice and fast way to replace plain text. But it isn't working for me if I want to replace a word with html. 1. It replaces also text inside < >. 2. If I have A and A++ and want to add for example a span tag around them then you end up with A++. Because it walks only onetime the content, so it doesn't know about the first span. But still very impressive code. So I still need somehow to improve my original code :/ – Oliver Jul 20 '20 at 20:00
  • @Oliver Ok I understand your problem, but sensibly replacing inner texts across tag boundaries is a whole different game. Doing this without screwing your html on the way is way more complex than simple text replacements. I'm sure there are a ton of questions and answers regarding this topic on this site. – Yoshi Jul 21 '20 at 07:39