javascript – 文本查找和替换chrome扩展 – 它们如何快速工作?

在我的chrome扩展程序中,我试图获取页面上的每个文本元素,检查它是什么,并替换它,如果它是那样的东西.这是我的第一种方法:

function textNodesUnder(el){
    var n, a=[], walk=document.createTreeWalker(el,NodeFilter.SHOW_TEXT,null,false);
    while(n=walk.nextNode()) a.push(n);
    return a;
}

const nodes = textNodesUnder(document.getElementsByClassName("content")[0]);

    for (let i = 0; i < nodes.length; i++) {
        // replace text
        for (let k in whatToReplace) {
            nodes[i].nodeValue = nodes[i].nodeValue.replace(new RegExp(k, "gi"), whatToReplace[k])
        }

工作,但非常非常慢 – 处理页面5-10秒.我更像是服务器端/ Golang开发人员,所以我意识到我可能会在这里咆哮错误的树,但是 – 大多数文本查找和替换样式chrome扩展如何快速工作?像Webworker这样的东西会派上用场吗?

最佳答案 一个很大的瓶颈可能就是你正在编译内部循环内部每次迭代的正则表达式.编译正则表达式并不会花费太长时间,但是当您为每个正在爬行的节点执行此操作乘以您拥有的替换对的数量时,它会相加.

您似乎将替换信息存储为对象,因此需要使用RegExp构造函数将字符串转换为正则表达式:

const whatToReplace = {
  ipsum: 'IPSUM',
  'Vivamus|vehicula': 'VROOM!',
  '^Donec': 'donut',
  'eros': 'lust',
  'semper': 'always'
};

相反,我会将它们存储为包含RegExp文字和它的替换文本的数组数组.

const whatToReplace = [
  [/ipsum/gi, 'IPSUM'],
  [/Vivamus|vehicula/, 'VROOM!'],
  [/^Donec/, 'donut'],
  [/eros/, 'lust'],
  [/semper/, 'always']
];

然后你可以使用for...of而不是for …来迭代它:

const whatToReplace = [
  [/ipsum/gi, 'IPSUM'],
  [/Vivamus|vehicula/gi, 'VROOM!'],
  [/^Donec/gi, 'donut'],
  [/eros/gi, 'lust'],
  [/semper/gi, 'always']
];

const contentNode = document.querySelector(".content");

let walk = document.createTreeWalker(contentNode,NodeFilter.SHOW_TEXT,null,false);
let node;
while((node = walk.nextNode())) {
  // replace text
  for (let [rx, replacement] of whatToReplace) {
    node.nodeValue = node.nodeValue.replace(rx, replacement);
  }
}

这段代码还可以节省一点时间和内存,只需在每个节点遍历树时改变每个节点,而不是将其存储在数组中,然后循环遍历它.因为我们只是在寻找带有.content类的第一个元素,所以我使用了querySelector而不是getElementsByClassName,因为它只查找一个元素而不是该类的所有元素,因此它也应该更快.

如果您不能将它们存储为文字,例如,如果您是从用户输入中获取它们,您仍然可以在循环之外预先编译它们一次:

let whatToReplace = {
  ipsum: 'IPSUM',
  'Vivamus|vehicula': 'VROOM!',
  '^Donec': 'donut',
  'eros': 'lust',
  'semper': 'always'
};

// convert whatToReplace into an array like the one in the previous example
whatToReplace = Object.entries(whatToReplace).reduce(function (acc, [key, value]) {
  acc.push([new RegExp(key, 'gi'), value])
  return acc;
}, []);

const contentNode = document.querySelector(".content");

let walk = document.createTreeWalker(contentNode,NodeFilter.SHOW_TEXT,null,false);
let node;
while((node = walk.nextNode())) {
  // replace text
  for (let [rx, replacement] of whatToReplace) {
    node.nodeValue = node.nodeValue.replace(rx, replacement);
  }
}

另一个瓶颈可能是你正在改变DOM的事实.每次更改节点时,都可能导致重新绘制和/或重排.这可能会破坏浏览器的性能.您可以通过首先删除要更改的DOM树的一部分来修复此问题,在DOM不在DOM中时更改它,然后将其重新插入DOM:

const whatToReplace = [
  [/ipsum/gi, 'IPSUM'],
  [/Vivamus|vehicula/gi, 'VROOM!'],
  [/^Donec/gi, 'donut'],
  [/eros/gi, 'lust'],
  [/semper/gi, 'always']
];

const contentNode = document.querySelector(".content");
const parent = contentNode.parentNode;
const placeholder = document.createElement('div');

// remove it from the DOM and replace it with a placeholder
parent.replaceChild(placeholder, contentNode);

let walk = document.createTreeWalker(contentNode,NodeFilter.SHOW_TEXT,null,false);
let node;
while((node = walk.nextNode())) {
  // replace text
  for (let [rx, replacement] of whatToReplace) {
    node.nodeValue = node.nodeValue.replace(rx, replacement);
  }
}

// swap our altered element back into the DOM
parent.replaceChild(contentNode, placeholder);

根据我创建的performance test,在Chrome中首先从DOM中删除它似乎没有产生巨大的差异,但它确实使它更快一点.如果你必须与Firefox打交道,那确实会有很大的不同.有趣的是,从DOM中删除它似乎在Edge中产生了5%的差异,但是由于某些原因,在对象上使用字符串而不是RegExp文字数组似乎更快.

进一步阅读

> Dynamic vs Inline RegExp performance in JavaScript
> Rendering: repaint, reflow/relayout, restyle
> The DOM isn’t slow, you are.
> Object.entries on MDN
> Array.prototype.reduce on MDN
> Destructuring assignment

点赞