文本查找并替换镀铬扩展名 - 它们的工作方式如此之快



在我的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扩展名如此迅速地工作?这是像网络工作者一样派上用场的地方吗?

一个大瓶颈可能是您正在编译该内部循环内部每个迭代的正则表达式。编译正则表达式不会花费太长时间,但是当您为每个节点进行操作时,您的爬行乘以乘以替换对的数量,它加起来了。

您似乎正在将替换信息存储为对象,因此必须使用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...in迭代它:

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中:

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);

根据我创建的性能测试,在Chrome中将其从DOM删除,似乎并没有产生巨大的不同,但确实使其更快。如果您必须处理Firefox,那确实会有很大的不同。有趣的是,将其从DOM中删除似乎在边缘差异5%,但是由于某种原因,在对象上使用字符串而不是Regexp文字的数组似乎更快。

进一步阅读

  • Dynamic与JavaScript中的Dynamic VS Inline Regexp性能
  • 渲染:重新粉刷,回流/递送,restyle
  • dom不慢,你是。
  • MDN上的Object.entries
  • MDN上的Array.prototype.reduce
  • 破坏分配

这是我推荐的:

  1. 不要将所有文本节点收集到一个数组中。这可能会导致一个巨大的数组。

  2. 可以通过一些缓冲技能重复使用Regexp对象。这可能有助于减少处理时间。

  3. 您可以跳过一些文本节点来节省执行时间。例如,跳过包含空/全空格文本的文本节点。

  4. 不需要节点阵列。文本节点可以一次通过Treewalker递归执行。

相关内容

最新更新