【问题标题】:Replacing a lot of text in browser's addon替换浏览器插件中的大量文本
【发布时间】:2016-06-01 07:46:26
【问题描述】:

我正在尝试开发一个 Firefox 插件,可以将任何页面上的文本音译成特定语言。实际上它只是我迭代并使用此代码的一组 2D 数组

function escapeRegExp(str) {
    return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}

function replaceAll(find, replace) {
    return document.body.innerHTML.replace(new RegExp(escapeRegExp(find), 'g'), replace);
}

function convert2latin() {
    for (var i = 0; i < Table.length; i++) {
        document.body.innerHTML = replaceAll(Table[i][1], Table[i][0]);
    }
}

它可以工作,我可以忽略 HTML 标签,因为它只能是英文,但问题是性能。当然,它非常非常贫穷。由于我没有 JS 经验,所以我尝试 google 并发现 documentFragment 可能有帮助。
也许我应该使用另一种方法?

【问题讨论】:

  • 您的Table 是动态的还是静态的?如果是动态的,它为什么会改变?它多久改变一次? Table 包含什么样的内容(例如,英文和拉丁字符的列表(猜想convert2latin)?)?您显然是在尝试适应 Table 包含一些特殊字符的可能性。 Table的内容来源是什么?请提供一些输入和输出的例子。我们需要知道您期望的输入和输出,以便评估解决该问题的其他潜在方法。
  • @Makyen 这是我使用的 python 测试脚本。 JS 表只是对其语法进行更改的复制粘贴。 github.com/Pugnator/JLPTrainer/blob/master/jpconvert.py 主要思想是遍历俄语文本并将每个音节转换为日语类似物。这个插件的目标是帮助人们以更简单的方式记住日文符号,而无需打磨
  • 您的 Python 代码使用了不使用正则表达式的 string.replace()。您使用正则表达式进行匹配是否有原因?如果您只使用普通字符串进行匹配,它应该会更快。如果您要使用 RegExp,看起来是一个您多次执行的静态替换数组,您通常最好只创建每个 RegExp 一次并将其存储(例如在Table[i][3] 中)以供使用而不是重新- 每次创建每个 RegExp(昂贵)。
  • @Makyen 不,没有具体原因。我是 web 和 JS 的新手。有人告诉我,造成巨大滞后的主要原因是每次替换时都要重建 DOM。这样它总是会滞后
  • 确实如此,它应该为您提供了足够的信息来显着提高性能。

标签: javascript performance firefox-addon


【解决方案1】:

根据您的 cmets,您似乎已经被告知最昂贵的事情是当您完全替换页面的全部内容时(即当您分配给 document.body.innerHTML)时发生的 DOM 重建。您目前正在为每个替换执行此操作。这会导致 Firefox 为您所做的每个替换重新渲染整个页面。完成所有替换后,您只需分配给document.body.innerHTML 一次。

以下内容应该是加快速度的第一步:

function escapeRegExp(str) {
    return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}

function convert2latin() {
    newInnerHTML = document.body.innerHTML
    for (let i = 0; i < Table.length; i++) {
        newInnerHTML = newInnerHTML.replace(new RegExp(escapeRegExp(Table[i][1]), 'g'), Table[i][0]);
    }
    document.body.innerHTML = newInnerHTML
}

您在 cmets 中提到没有真正需要使用 RegExp 进行匹配,因此以下内容会更快:

function convert2latin() {
    newInnerHTML = document.body.innerHTML
    for (let i = 0; i < Table.length; i++) {
        newInnerHTML = newInnerHTML.replace(Table[i][1], Table[i][0]);
    }
    document.body.innerHTML = newInnerHTML
}

如果您确实需要使用正则表达式进行匹配,并且要多次执行这些精确替换,则最好在第一次使用之前创建所有正则表达式(例如,当创建 Table 时/已更改)并存储它们(例如在 Table[i][2] 中)。

但是,分配给document.body.innerHTML 是一种不好的做法:

正如 8472 所提到的,替换 document.body.innerHTML 的全部内容是执行此任务的一种非常笨拙的方式,它有一些明显的缺点,包括可能破坏页面中其他 JavaScript 的功能和潜在的安全问题。更好的解决方案是仅更改文本节点的textContent

一种方法是使用TreeWalker。这样做的代码可能是这样的:

function convert2latin(text) {
    for (let i = 0; i < Table.length; i++) {
        text = text.replace(Table[i][1], Table[i][0]);
    }
    return text
}

//Create the TreeWalker
let treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT,{
    acceptNode: function(node) { 
        if(node.textContent.length === 0
            || node.parentNode.nodeName === 'SCRIPT' 
            || node.parentNode.nodeName === 'STYLE'
        ) {
            //Don't include 0 length, <script>, or <style> text nodes.
            return NodeFilter.FILTER_SKIP;
        } //else
        return NodeFilter.FILTER_ACCEPT;
    }
}, false );
//Make a list of nodes prior to modifying the DOM. Once the DOM is modified the TreeWalker
//  can become invalid (i.e. stop after the first modification). Doing so is not needed
//  in this case, but is a good habit for when it is needed.
let nodeList=[];
while(treeWalker.nextNode()) {
    nodeList.push(treeWalker.currentNode);
}
//Iterate over all text nodes, changing the textContent of the text nodes 
nodeList.forEach(function(el){

    el.textContent = convert2latin(el.textContent));
});

【讨论】:

  • @pugnator,添加了显示 TreeWalker 更改文档中所有文本节点的 textContent 的代码。
  • @pugnator,Firefox 倾向于在 DOM 中有大量空白文本节点。我已经添加了跳过那些,所以时间不会花在尝试对空字符串进行更改上。这还显示了TreeWalkeracceptNode 函数的基本用法,可根据节点的属性选择要处理的节点,而不仅仅是TreeWalker.whatToShow 选择的类型。
  • 我刚刚检查过它,它就像一个魅力。即使是沉重的维基百科页面也刷新得非常快(之前大约是 30 秒)。 TreeWalker 做到了。现在我可以专注于为学生做更多有用的事情。再次感谢您
  • “但是,这样做会导致对每个更改的节点进行部分页面重新布局的性能损失。” - 不会。修改 DOM 只会标记布局一样脏,它不会立即重新布局页面,除非您读取一些与样式相关的属性或 javascript 退回到 UI 事件循环。请参阅phpied.com/rendering-repaint-reflowrelayout-restyle 向下滚动查看 Browsers are smart 部分。而且您仍然缺少 setTimeout 部分来减少卡顿。
  • 另外,您可能希望跳过只有空格的节点,而不仅仅是空节点。
【解决方案2】:

不要使用 innerhtml,它会破坏在 DOM 节点上注册的任何 javascript 事件处理程序,或者使对页面的 javascript 持有的 dom 节点的引用已过时。换句话说,你可以很容易地用它来打破一个页面。当然效率低下。

您可以改用treewalker 并仅过滤文本节点。可以通过每 1000 个文本节点或类似的方式使用 window.setTimeout 推迟下一步来增加步行。

如果您足够早地注册插件脚本,您还可以使用 mutation observer 在插入文本节点后立即收到有关文本节点的通知,并逐步替换它们,这样可以减少麻烦。

【讨论】:

    猜你喜欢
    • 2015-10-19
    • 2015-08-28
    • 2012-06-25
    • 1970-01-01
    • 1970-01-01
    • 2010-10-19
    • 2016-05-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多