【问题标题】:Is there a way to create out of DOM elements in Web Worker?有没有办法在 Web Worker 中创建 DOM 元素?
【发布时间】:2013-08-06 02:05:10
【问题描述】:

背景: 我有一个处理和显示大量日志文件的 Web 应用程序。它们通常只有大约 100k 行,但可以达到 400 万行或更多。为了能够滚动浏览该日志文件(用户启动和通过 JavaScript)并过滤具有良好性能的行,我在数据到达后立即为每一行创建一个 DOM 元素(通过 ajax 在 JSON 中)。我发现这对性能更好,然后在后端构建 HTML。之后我将元素保存在一个数组中,我只显示可见的行。

对于最多 100k 行,这只需大约几秒钟,但对于 500k 行(不包括下载),其他任何操作最多需要一分钟。我想进一步提高性能,所以我尝试使用 HTML5 Web Workers。现在的问题是我无法在 Web Worker 中创建元素,甚至在 DOM 之外。所以我最终只在 Web Workers 中进行 json 到 HTML 的转换,并将结果发送到主线程。它在那里被创建并存储在一个数组中。不幸的是,这降低了性能,现在至少需要多花 30 秒。

问题:那么,有什么我不知道的方法可以在 DOM 树之外,在 Web Worker 中创建 DOM 元素吗?如果不是,为什么不呢?在我看来,这不会产生并发问题,因为创建元素可以并行发生而不会出现问题。

【问题讨论】:

  • 您是否想过一种解决方案,即按需请求日志行?一次解析 4M 行日志文件是一项繁重的任务,即使您可以在这里以有效的方式使用 WebWorker,也无法获得所需的性能提升。我建议只请求一堆行并像这些无限滚动页面一样处理它们。
  • 是的,但这会使过滤选项的实现变得非常困难。我还必须存储 JSON 格式,因为在后端将其解析为 JSON 也需要一分钟,而且没有办法分段完成。它还会降低滚动和过滤性能,这需要几毫秒甚至纳秒。

标签: javascript jquery html web-worker


【解决方案1】:

好的,我根据@Bergi 提供的信息做了更多的研究,发现了关于 W3C 邮件列表的以下讨论:

http://w3-org.9356.n7.nabble.com/Limited-DOM-in-Web-Workers-td44284.html

以及回答为什么在 Web Worker 中无法访问 XML 解析器或 DOM 解析器的摘录:

您假设没有任何 DOM 实现代码使用任何排序 非 DOM 对象,永远,或者如果它确实这些对象是完全的 线程安全的。情况并非如此,至少在 Gecko 中是这样。

在这种情况下的问题是不同的 DOM 对象被触及 多个线程。问题是不同线程上的两个 DOM 对象 两者都接触到一些全球性的第三个物体。

例如,XML 解析器必须做一些 Gecko 中可以做的事情 只能在主线程上完成(DTD 加载,副手;有一个 其他一些我以前见过但不记得的)。

但也提到了一种解决方法,它使用解析器的第三方实现,其中jsdom 是一个示例。有了它,您甚至可以访问自己的单独文档。

【讨论】:

  • 邮件列表上的讨论似乎只是在挥手。当然,如果当前版本的Gecko做不到,那也没关系,但这并不意味着未来版本也应该有这个限制。跨度>
  • 似乎对它有足够的兴趣,有朝一日它可能会成为可能:2ality.com/2012/11/canvas-in-workers.html
  • 这就是虚拟 DOM 如此出色的原因。 U 可以在另一个线程中进行复杂的检查和渲染操作。
  • 我想在 worker 中构建一个巨大的 HTML 表格以使页面更具响应性,所以 jsdom 看起来很有希望——甚至还有 @types/jsdom。但是,我遇到了很多问题,包括 stackoverflow.com/questions/68592278/…,最后只是在 worker 中生成了原始 HTML。
【解决方案2】:

那么有什么我不知道的方法可以在 DOM 树之外的 Web Worker 中创建 DOM 元素吗?

没有。

为什么不呢?在我看来,这不会产生并发问题,因为创建元素可以并行发生而不会出现问题。

不是为了创造它们,你是对的。但是为了将它们附加到主 document - 它们需要被发送到不同的内存(就像 blob 一样),以便之后工作人员无法访问它们。但是,there's absolutely no Document handling available in WebWorkers

一旦数据到达(通过 ajax 在 JSON 中),我就为每一行创建一个 DOM 元素。之后我将元素保存在一个数组中,我只显示可见的行。

构建超过 500k 的 DOM 元素是一项艰巨的任务。尝试仅为可见的行创建 DOM 元素。为了提高性能并更快地显示前几行,您还可以将它们的处理分成更小的单元并在其间使用超时。见How to stop intense Javascript loop from freezing the browser

【讨论】:

  • 动态创建 DOM 元素的一个问题是 atm 我使用元素来保存多个数据值,例如索引。因为在过滤行时索引会发生变化,所以我会在需要索引的地方保留对元素的引用。我过去也曾在那些地方更新它,但是因为它们很多,所以花了太长时间。如果我还有足够的时间,那么我可能会尝试你的方式,看看我是否不能以另一种方式存储对索引(和其他值)的引用。
  • 如何将所有行保留为 JavaScript 对象,并且只保留屏幕上的空间数量的 DOM 对象?滚动时,您更改映射 DOM 对象 -> JavaScript 对象,而不是更改哪些 DOM 对象可见。这将使您可以非常快速地过滤和滚动,并避免创建过多的 DOM 对象。
  • @JonWatte:是的,这就是我的第二个建议的意思。这就是高性能的表格滚动器库所做的
【解决方案3】:

您必须了解网络工作者的本质。使用线程编程是困难,尤其是在共享内存时;奇怪的事情可能发生。 JavaScript 不具备处理任何类型的线程式交错的能力。

webworkers 的做法是没有共享内存。这显然会导致您无法访问 DOM 的结论。

【讨论】:

  • 我意识到,但是要在 DOM 树之外创建元素,就不需要共享内存,不是吗?唯一需要共享的是创建元素的逻辑,我猜,如果需要,可以复制。
  • 您通过document 访问DOM 这是一个全局变量,所以.. 没有:(
  • @FritsvanCampen - 您可以通过 any 文档对象访问 DOM API,而不仅仅是附加到窗口对象的那个。所以它不必是一个全局变量。
  • @Alohci 我明白了,但是您将如何获取新文档以及如何转移节点?我怀疑importNode 会在这方面起作用。另外,制作 DOM 节点并不昂贵,只需将一些数据传回并在父脚本中进行 DOM 转换即可。
  • @FritsvanCampen 您无法从 WebWorker 访问浏览器实现的 nativ-dom-api,在这一点上您是对的。然而,Alohci 的方法是更理论上指向自定义 js 实现,这就是我理解他的方式。关于在 WebWorker 和原始文档之间传输节点:这只能通过消息系统完成,并且需要某种序列化。 HTML 是显而易见的选择,但提到 OP,这种技术的效果并不好。
【解决方案4】:

没有通过 Web Workers 直接访问 DOM 的方法。 我最近发布了@cycle/sandbox,它仍然是 WIP,但它证明了 Cycle JS 架构在 Web Worker 中声明 UI 行为是相当直接的。实际的 DOM 仅在主线程中被触及,但事件侦听器和 DOM 更新是在 worker 中间接声明的,并且当这些侦听器发生某些事情时会发送一个合成的事件对象。此外,将这些沙盒循环组件并排安装常规循环组件非常简单。

http://github.com/aronallen/-cycle-sandbox/

【讨论】:

    【解决方案5】:

    我看不出有什么理由不能使用 web-workers 构造 html 字符串。但我也不认为会有很大的性能提升。

    这与 Web-Workers 无关,但与您要解决的问题有关。以下是一些可能有助于加快速度的事情:

    1. 使用 DocumentFragments。在数据进入时向它们添加元素,并以一定的时间间隔(例如每秒一次)将片段添加到 DOM。这样您就不必在每次加载一行文本时都触摸 DOM(并导致重绘)。

    2. 在后台加载,仅在用户点击滚动区域底部时解析行。

    【讨论】:

    • 关于使用定时函数的第一个建议将产生与您预期相反的效果。浏览器不会在每次 DOM 操作之后重绘或渲染,而是在调用堆栈变空以及从队列中获取下一个异步任务之前。这意味着当您将任务拆分为较小的异步部分时,您将产生比所需更多的渲染调用,这些部分都添加到队列中。这与 javascript 事件循环有关。
    • 您的第二个建议通常称为按需获取数据。这意味着仅在需要时获取和处理它们。和恕我直言。这将是解决此问题的最可靠解决方案,但是 OP 已经根据他的问题下的 cmets 中的此解决方案抱怨了其他问题。
    • 他将元素添加到 dom 的方式,会导致最大次数的重绘。 DocumentFragments 可以在屏幕外创建和操作。通过跳过可能发生重绘的帧,他节省了一些处理器周期。我并不是建议他按需加载数据。我建议他在后台加载数据,并仅在需要时将其解析为 dom 元素。至于过滤,我建议对元素应用类,并根据过滤器的规则设置它们的显示
    • 啊,好吧,我对“重绘”和“重新渲染”这两个术语感到有些困惑。让我们在这里澄清一下:“重绘”是指浏览器实际更新屏幕的过程,而“重新渲染”可以在后台发生。例如,如果您更改了当前附加到文档且可见的元素的大小,然后访问“offsetWidth”属性,则浏览器需要执行“重新渲染”以确定新的计算属性,但是更改不会直接显示在屏幕上。
    • 创建 DOM 元素不会导致重绘或重新渲染,除非它们被添加到实际的 DOM 树中。使用 DocumentFragments 或将元素附加到另一个分离的元素基本相同。
    【解决方案6】:

    不幸的是,根据https://developer.mozilla.org/en-US/docs/Web/Guide/Performance/Using_web_workers,Web Worker 无法访问 DOM。

    【讨论】:

      【解决方案7】:

      您的设计中有几个反模式:

      1. 创建 DOM 对象有相当大的开销,而且您正在 一次创建可能数百万个
      2. 尝试让 web worker 来管理 DOM 正是 web 工人不适合。他们做所有其他事情,以便 DOM 事件循环保持响应。

      您可以使用光标模式滚动任意大组数据。

      1. DOM 向工作人员发送一条消息,其中包含开始位置和请求的行数(光标)。
      2. Web Worker 随机访问日志,回发获取的行(光标数据)。
      3. DOM 使用异步光标响应事件更新元素。

      这样,繁重的工作由工作人员完成,其事件循环在获取期间被阻塞而不是 DOM,导致快乐的非阻塞用户惊叹于所有动画的流畅度。

      【讨论】:

        【解决方案8】:

        因此,您不能直接在 webworker 中创建 DOM - 但是,可能还有另一种选择可以在主线程之外进行相当多的处理。

        查看我刚刚创建的这个 jsPerf:http://jsperf.com/dom-construction-obj-vs-str

        基本上,您可以发出与从 DOM 获得的所有相同值的 POJSO,并在收到消息后将其转换为 DOM 对象(这就是您在收到消息时正在做的事情毕竟返回 HTML;POJSO 的开销较低,因为不需要进一步的字符串处理)。通过这种方式,您甚至可以执行诸如发出事件侦听器之类的操作(例如,在事件名称前加上“!”,并将值映射到某个模板提供的视图参数)。

        同时,如果没有可用的 DOM 解析器,您将需要自己的东西来根据需要转换模板,或者将模板编译为快速的格式。

        【讨论】:

        • 您确定factory.removeChild(ret); 不会影响您的测试吗?
        • 似乎没有。 JSPerf 这些天有点破,所以我无法编辑旧的:jsperf.com/dom-construction-3
        • 顺便说一句,即使添加从字符串解析 JSON 的步骤也不会使 Object->DOM 案例比标准 HTML 解析案例更糟糕。 jsperf.com/dom-construction-4.
        【解决方案9】:

        不,您不能在 Web Worker 中创建 DOM 元素,但您可以创建一个函数来接受来自该 Web Worker 的发布消息,即does 创建 DOM 元素。我认为您正在寻找的设计称为阵列卡盘。您需要将其与 Web Worker 设计模式混合使用。

        【讨论】:

          猜你喜欢
          • 2017-11-29
          • 2011-04-30
          • 1970-01-01
          • 1970-01-01
          • 2013-08-20
          • 1970-01-01
          • 1970-01-01
          • 2020-09-15
          • 2011-01-23
          相关资源
          最近更新 更多