【问题标题】:Executing multiple DOM updates with JavaScript efficiently使用 JavaScript 高效执行多个 DOM 更新
【发布时间】:2016-05-04 23:14:44
【问题描述】:

我有一个包含数百个 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();

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

【问题讨论】:

  • 在您处理完任何给定事件之前,浏览器是否会重新显示任何内容?似乎这样应该可以完成大部分工作。
  • 最多有 1300 个 HTML 元素,其中最多 260 个应该在 4 毫秒内更新(在最坏的情况下)。
  • 好的,但是您是否尝试过以直接的方式执行此操作,并确定这会导致数百次回流?
  • 不,还没有——我想先以正确的方式做这件事,然后再以艰难的方式去做。即使可能以直接的方式工作,我也希望以最高效的方式。
  • "每秒最多 250 次" - 呃,什么显示器会更新得这么快?浏览器不会比必要的更频繁地呈现。而且你不能真的强迫它与JS有关。

标签: javascript performance dom


【解决方案1】:

您并没有具体说明您正在做什么,所以我们在这里能做的最好的事情就是为您提供有关一系列相关领域的一般性建议。

DOM 修改

你没有完全填写你想要做的事情的所有细节,但在优化你的代码工作方式时,这里有很多事情需要考虑:

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

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

  3. 一次批量处理所有 DOM 更改。 最糟糕的做法是进行 DOM 更改,使用计时器等待几毫秒,再进行 DOM 更改,等待几秒钟ms 与计时器等等,因为您将进行 DOM 更改、重排、重绘、DOM 更改、重排、重绘等......相反,请确保您在一个同步的 Javascript 中一次完成所有待处理的 DOM 更改。然后,这将允许浏览器对重排和重绘进行排队,并且在您完成所有 DOM 更改后只执行一次。如果您想更智能地处理批处理,您将折叠对同一元素的修改,以便仅使用最终值处理一次。因此,如果 elementA 首先被赋予新值 3,然后在同一批次中被赋予新值 4,那么在处理这批数据时,您希望跳过 3 而只处理 4。

  4. DOM 修改有时可以得到优化。您没有向我们提供有关如何修改 DOM 的任何细节,但如果您修改复杂的对象(例如添加许多表格行或更改大量表格单元格),那么有时最好创建一组当前未插入 DOM 的 DOM 元素,对它们进行所有修改,然后通过一次操作将它们插入 DOM。这是因为修改当前插入到 DOM 中的 DOM 元素会迫使浏览器确定哪些其他 DOM 元素受到此更改的影响,因此它可以将适当的重排或重绘排队。但是修改屏幕外的 DOM 元素不必做任何工作,直到最后一步将一个更大的对象插入到 DOM 中。这里没有有用的通用规则,因为您如何利用此优化完全取决于您正在执行的 DOM 修改。我们可以在这方面为您提供帮助,但前提是我们可以同时查看您拥有的 HTML 以及您对其所做的更改。

更新时间

现在,关于事情的时间安排,您提到每秒最多 250 次。对于用户可见的东西来说,这有点快(每次操作 4 毫秒)。如果你这样做,你的浏览器基本上会不断地重排和重绘,只是偶尔会暂停以处理其他用户事件。由于没有用户能真正看到从现在开始在 4ms、8ms 和 12ms 发生的事情,因此真的不需要经常更新屏幕上的状态。

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

有几种批量更新的实施策略。如果您的修改总是不断流入,我能想到的最简单的方法是在修改进入时将其放入本地数组,然后为您的更新间隔设置一个间隔计时器,以检查数组以及其中是否有任何内容,它将数组中的所有项目处理成一个 DOM 更新。

从服务器获取更改

您没有提到浏览器 Javascript 是如何获取新数据的。一般有两种选择,重复Ajax请求或者创建一个webSocket,服务器可以随时直接给你发送数据。

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

如果您打算使用 Ajax 进行轮询,那么我强烈建议您延长轮询间隔。非常短的轮询间隔会真正加载您的服务器并消耗客户端的电池。

电池

如果此应用打算在电池供电的设备上长时间运行,那么从任何服务器实时获取稳定的数据流(例如,像您提到的那样每隔几毫秒)就会消耗电池电量,因为无线电(WiFi 或蜂窝网络)几乎一直处于活动状态,CPU 也会大量运行。

【讨论】:

  • 关于第 4 点:确定的唯一方法是测试。应该实现多种解决方案(in-DOM 更新、ex-DOM 更新、display:none-updates)并在真实应用程序中使用真实数据对它们进行基准测试。
  • @Bergi - 是的,同意。对于寻求某种性能优化的大多数更改都是如此,对于更新 DOM 多个部分的不同方案当然也是如此。
  • 你甚至不需要把实现扔到墙上来看看有什么效果。 Chrome 的 DevTools 具有深入的性能工具,因此您可以实际查看浏览器花费的时间。 “编写内容并对其进行分析”对于网络应用程序与任何其他平台一样有效。
  • 您好!参考您在 3 批处理中提出的观点:这种延迟回流和重绘是如何工作的?我的观点是:我正在使用 jquery 来制作一堆 $(e).html(new_data)。你是说如果我循环过去,我可以放心,回流和重绘总是会延迟到我的循环完成?我应该寻找哪些文档来查找此类信息?谢谢
  • @user3617487 - 除了少数例外(没有真正记录,因为它们只是浏览器渲染优化,不是某些标准的一部分),浏览器不会尝试重排和重绘页面,直到您的 Javascript 将控制权返回给系统(完成它正在执行的操作)。
【解决方案2】:

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

https://github.com/wilsonpage/fastdom

FastDom 充当您的应用/库和 DOM 之间的监管层。通过批处理 DOM 访问,我们避免了不必要的文档重排并显着提高了布局性能。

每个测量/变异作业都被添加到相应的测量/变异队列中。使用 window.requestAnimationFrame 在下一帧轮到队列被清空(读取,然后写入)。

库大小为 0.6kb gzipped/minified。

【讨论】:

    【解决方案3】:

    您似乎已经具备了想要做的事情的基础知识。对于您的特定用例,我认为您没有理由不天真地实现这些方法。

    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 属性。

    【讨论】:

    • 他在问什么是高性能的。我的意思是我猜几十个“每秒最多四次”对浏览器来说并不是一个巨大的负载。哈哈
    • “每秒最多 4 次”对我来说是一个错误 - 我的意思是每 4 毫秒,即“每秒最多 250 次”。
    • 您可能想return {collectDomUpdate,applyUpdates}; 以便可以使用这些功能...
    【解决方案4】:

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

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

    【讨论】:

    • 复制或克隆节点也可能对监听器产生影响。
    猜你喜欢
    • 2018-06-19
    • 1970-01-01
    • 2012-07-07
    • 2021-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多