文本查找并替换chrome扩展 - 它们如何快速工作?

时间:2018-04-02 22:08:07

标签: javascript google-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这样的东西会派上用场吗?

2 个答案:

答案 0 :(得分:1)

一个很大的瓶颈可能就是你正在内循环内部的每次迭代中编译正则表达式。编译正则表达式不会花费太长时间,但是当你为每个正在爬行的节点执行它时,再乘以你拥有的替换对的数量,就会增加。

您似乎将替换信息存储为对象,因此需要使用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中时将其更改,然后将其重新插入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文字数组上使用字符串似乎更快。

进一步阅读

答案 1 :(得分:0)

以下是我的建议:

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

  2. regexp对象可以通过一些缓冲技能重用。这可能有助于缩短处理时间。

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

  4. 不需要节点数组。文本节点可以通过treeWalker一次一个地执行。