使用JavaScript高效执行多个DOM更新

时间:2016-05-04 23:14:44

标签: javascript performance dom

我有一个包含数百个div元素的HTML / JS网站。应该一次快速更新几十个这些元素(每秒最多250次)(即它们应该一次更新,而不需要浏览器通过逐个执行更新来执行不必要的工作一)。什么是最高性能的方法(最小化CPU负载和内存消耗),以支持现代浏览器使用纯JavaScript或简单的库(不使用React或类似的库,要求我修改代码)来实现这一点超越DOM处理)?我正在寻找类似这样的东西(imaginaryLibrary是我不知道的库,我正在寻找):

var i, element;
for(i = 0; i < 20; i++){
    element = document.getElementById('el' + i);
    imaginaryLibrary.collectDomUpdate(element, cssClass, 'updatedCssClass');
}
//executes DOM updates collected in loop before and applies the DOM updates in one reflow
imaginaryLibrary.applyUpdates();

要更新的元素的父元素包含数千个不应更新的元素。

4 个答案:

答案 0 :(得分:21)

你并不确切地知道你正在做什么,所以我们能做的最好的事情就是给你一些相关领域的一般建议。

DOM修改

您没有完全填写您尝试做的所有细节,但在优化代码的工作方式时,需要考虑以下几点:

  1. 回流已排队。浏览器已尝试最小化回流,因此如果您在一个连续的Javascript中执行四次dom修改,浏览器将等待该Javascript的一部分完成跑步,然后做一次回流和一次重画。

  2. 请求某些属性可以触发立即重排。您希望避免上述规则的例外情况。例如,如果您请求某些DOM属性需要正确布局以准确报告属性值,并且先前的修改中存在待处理的布局,则浏览器可以在返回您请求的属性之前同步地重新布局文档。这些类型的属性通常涉及屏幕位置和其他属性,这些属性显然会受到文档布局的影响。如果您想了解更多详细信息,有很多关于此主题的文章。在许多情况下,您的代码无论如何都不会使用这些属性,但如果是这样,通常的解决方法是在对DOM进行任何更改之前先请求所有需要的属性。

  3. 立即批量处理所有DOM更改。最糟糕的事情是进行DOM更改,使用计时器等待几毫秒,进行另一个DOM更改,等待一些带有计时器的ms等等,因为你将进行DOM更改,重排,重绘,DOM更改,重排,重新绘制等等...相反,请确保您在一个同步段中同时执行所有挂起的DOM更改的JavaScript。然后,这将允许浏览器对重排进行排队并重新绘制,并在您完成所有DOM更改后再执行一次。如果您想更好地了解批处理,您将折叠对同一元素的修改,以便仅使用最终值处理一次。因此,如果elementA首先被赋予新值3,然后在同一批次中被赋予值4,则在处理该批数据时,您希望跳过3并仅处理4。

    < / LI>
  4. 有时可以优化DOM修改。您没有提供有关如何修改DOM的任何细节,但是如果您要修改复杂对象(例如添加许多表)行或更改大量表格单元格,然后有时最好创建一组当前未插入DOM的DOM元素,对它们进行所有修改,然后通过一个操作将它们插入到DOM中。这是因为修改当前插入DOM中的DOM元素会强制浏览器确定哪些其他DOM元素受此更改影响,因此它可以排队相应的重排或重新绘制。但是,在将一个较大的对象插入DOM的最后一步之前,修改屏幕外DOM元素不必进行任何工作。这里没有有用的通用规则,因为您如何利用此优化完全取决于您正在进行的DOM修改。我们可以帮助您解决这个问题,但只有当我们能够看到您拥有的HTML以及您正在做出哪些更改时才能帮助您。

  5. 更新时间

    现在,至于事情的时间安排,你提到每秒最多250次。对于用户可见的事情,这种快速(每次操作4ms)。如果你这样做,你的浏览器基本上将不断回流和重新绘制,只是偶尔暂停来处理其他用户事件。由于没有用户能够在4ms,8ms和12ms之后实际看到发生的事情,所以通常不需要在屏幕上更新状态。

    因此,如果您确实有快速或经常进行更改,您可能希望通过将它们累积到本地数据结构中来批处理它们,然后每100-500ms左右更新一次屏幕。你必须试验你可以使用的长间隔,而不是真正注意到任何延迟。

    批处理更新有多种实施策略。最简单的我可以想到,如果你的修改总是在不断地流式传输,只需将修改放入一个本地数组中,然后只需为你的更新间隔设置一个间隔计时器来检查数组,如果有的话。在其中的任何内容,它将数组中的所有项目处理为DOM更新。

    从服务器获取更改

    您没有提及浏览器Javascript如何获取新数据。通常有两个选项,重复Ajax请求或创建webSocket,服务器可以随时直接向您发送数据。

    如果您的更新定期且间隔很短,那么webSocket连接显然是最有效的。它是一个持续连接,只要服务器有新数据发送,服务器就可以向每个客户端发送新数据。

    如果您要使用Ajax进行轮询,那么我强烈建议您延长轮询间隔。非常短的轮询间隔将真正加载您的服务器并在客户端上吃掉电池。

    <强>电池

    如果这个应用程序打算在电池供电的设备上长时间运行,那么从任何服务器实时获取稳定的数据流(例如,如你所提到的每隔几毫秒)就会吃掉电池,因为无线电(WiFi或蜂窝电话)几乎一直处于活动状态,CPU也将运行很多。

答案 1 :(得分:0)

好像你已经掌握了你想要做的事情的基础知识。对于您的特定用例,我认为没有理由不应该天真地实现这些方法。

var imaginaryLibrary = function() {
    var toProcess = [];

    function collectDomUpdate(el, prop, newValue) {
        toProcess.push([el, prop, newValue])
    }

    function applyUpdates() {
        while (toProcess.length) {
            var actions = toProcess.pop()
            actions[0][actions[1]] = actions[2]
        }
    }
}

上面的代码是您可能想要执行的操作的框架 - 它不包含错误检查并直接分配给对象,而不是可能使用DOM属性。

答案 2 :(得分:0)

您需要“脱机”对DOM进行更新。根据您的要求,可能是在父容器的CSS中使用display:None,执行更新,然后取消隐藏所述容器。您可以通过从DOM中删除节点并在操作后重新插入它们来手动执行此类操作。

如果这会导致元素显示中出现明显或不可接受的间隙,那么您可能需要考虑一种解决方案,即复制节点,操作节点,然后覆盖旧节点。可能必须要小心并做一些研究,以确保不仅仅导致浏览器一直实例化一堆新元素。也许是节点的“池”

答案 3 :(得分:0)

这似乎与您正在寻找的虚构库相似,除了它在每个rAF之后自动应用更改,而不是手动处理它的开发人员:

https://github.com/wilsonpage/fastdom

  

FastDom充当您的app / library和DOM之间的监管层。通过批量访问DOM,我们避免了不必要的文档重排,并大大加快了布局性能。

     

每个measure / mutate作业都会添加到相应的measure / mutate队列中。使用window.requestAnimationFrame在下一帧的转弯处清空队列(读取,然后写入)。

图书馆大小为0.6kb gzipped / minified。