有没有办法在 Web Worker 中创建 DOM 元素?

IT技术 javascript jquery html web-worker
2021-02-26 22:55:37

上下文: 我有一个 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 元素?如果没有,为什么不呢?在我看来,这不会造成并发问题,因为创建元素可以毫无问题地并行发生。

6个回答

好的,我对@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 做不到,那也没关系,但这并不意味着未来的版本也应该有这个限制。
2021-05-03 22:55:37
这就是虚拟 DOM 如此伟大的原因。你可以在另一个线程中进行复杂的检查和渲染操作。
2021-05-03 22:55:37
我想在 worker 中构建一个巨大的 HTML 表,使页面更具响应性,所以jsdom看起来很有希望——甚至还有@types/jsdom。然而,我遇到了很多障碍,包括stackoverflow.com/questions/68592278/...,最后只在 worker 中生成了原始 HTML。
2021-05-08 22:55:37
似乎对它有足够的兴趣,以至于有一天它可能成为一种可能性:2ality.com/2012/11/canvas-in-workers.html
2021-05-17 22:55:37

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

不。

为什么不?在我看来,这不会造成并发问题,因为创建元素可以毫无问题地并行发生。

不是为了创造它们,你是对的。但是为了将它们附加到 main document- 它们需要被发送到不同的内存(就像 blob 可能的那样),以便此后工作人员无法访问它们。但是,WebWorkers 中绝对没有可用的文档处理

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

构建超过 50 万个 DOM 元素是一项繁重的任务。尝试只为可见的行创建 DOM 元素。为了提高性能并更快地显示前几行,您还可以将它们的处理分成更小的单元并在它们之间使用超时。请参阅如何阻止密集的 Javascript 循环冻结浏览器

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

你必须了解网络工作者的本质。使用线程编程很困难,尤其是在共享内存的情况下;奇怪的事情可能会发生。JavaScript 无法处理任何类型的类似线程的交错。

网络工作者的做法是没有共享内存这显然会导致无法访问 DOM 的结论。

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

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

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

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

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

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

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

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