为什么 JavaScript 在普通浏览器中没有自己的线程?

Why doesn't JavaScript get its own thread in common browsers?

JavaScript 不是多线程还不够,显然 JavaScript 甚至没有自己的线程,而是与其他东西共享一个线程。即使在大多数现代浏览器中,JavaScript 通常与绘画、更新样式和处理用户操作处于同一队列中。

这是为什么?

根据我的经验,如果 JavaScript 运行 在自己的线程上,单独通过 JS 不阻塞 UI 呈现或解放复杂或有限的,可以获得极大改善的用户体验消息队列优化样板文件(是的,还有你,网络工作者!),开发人员必须自己编写这些样板文件,以在真正涉及到它时保持 UI 到处响应。

我很想了解这种看似不幸的设计决策的动机,从软件架构的角度来看是否有令人信服的理由?

用户操作需要 JS 事件处理程序的参与

用户操作可以触发 Javascript 事件(点击、焦点事件、按键事件等)参与并可能影响用户操作,因此很明显单个 JS 线程不能在用户执行时执行正在处理操作,因为如果是这样,则 JS 线程无法参与用户操作,因为它已经在做其他事情。因此,在 JS 线程可用于参与该过程之前,浏览器不会处理默认用户操作。

渲染

渲染比较复杂。一个典型的 DOM 修改序列是这样的:1) DOM 被 JS 修改,布局标记为脏,2) JS 线程完成执行所以浏览器现在知道 JS 完成修改 DOM , 3) 浏览器将布局更改为重新布局 DOM, 4) 浏览器根据需要绘制屏幕。

步骤 2) 在这里很重要。如果浏览器在每次 JS DOM 修改后都进行新的布局和屏幕绘制,如果 JS 实际上要进行一堆 DOM 修改,则整个过程的效率可能会非常低。另外,还会有线程同步问题,因为如果你让 JS 在浏览器尝试重新布局和重绘的同时修改 DOM,你就必须同步那个 activity(例如阻止某人以便操作可以完成而底层数据不会被另一个线程更改)。

仅供参考,有一些解决方法可用于强制重新布局或强制从您的 JS 代码中重新绘制(不完全是您所要求的,但在某些情况下很有用)。

多线程访问DOM非常复杂

DOM本质上是一个大的共享数据结构。浏览器在解析页面时构造它。然后加载脚本和各种JS事件有机会修改它。

如果你突然有多个 JS 线程同时访问 DOM 运行ning,你会遇到一个非常复杂的问题。您将如何同步访问?您甚至无法编写涉及在页面中查找 DOM 对象然后修改它的最基本的 DOM 操作,因为那不是原子操作。在您找到 DOM 对象和进行修改之间,DOM 可能会发生变化。相反,您可能必须至少在 DOM 中的一个子树上获取一个锁,以防止在您操作或搜索它时它被其他线程更改。然后,在进行修改后,您必须释放锁并从您的代码中释放对 DOM 状态的任何了解(因为一旦您释放锁,其他线程可能会更改它) .而且,如果你没有正确地做事,你可能会陷入僵局或各种讨厌的错误。实际上,您必须将 DOM 视为并发的多用户数据存储。这将是一个复杂得多的编程模型。

避免复杂

"single threaded JS" 设计决策中有一个统一的主题。 保持简单。不需要了解多线程环境和线程同步工具以及多线程调试就可以编写可靠的浏览器 Javascript.

浏览器 Javascript 是一个成功平台的一个原因是它对所有级别的开发人员来说都非常容易访问,而且相对容易学习和编写可靠的代码。虽然浏览器 JS 可能会随着时间的推移获得更多高级功能(就像我们使用 WebWorkers 一样),但您可以绝对确定这些将以简单的事情保持简单的方式完成,而更高级的事情可以由更高级的开发人员完成,但没有现在打破任何使事情变得简单的事情。

仅供参考,我在 node.js 中编写了一个多用户 Web 服务器应用程序,我一直惊讶于由于 nodejs 的单线程特性,服务器设计的复杂性大大降低 Javascript.是的,有一些东西写起来更痛苦(学习编写大量异步代码的承诺),但是哇,你的 JS 代码永远不会被另一个请求中断的简化假设大大简化了设计、测试并减少了很难找到并修复并发设计和编码总是充满的错误。

讨论

当然,第一个问题可以通过允许用户操作事件处理程序在他们自己的线程中 运行 来解决,这样它们就可以随时发生。但是,然后您立即拥有多线程 Javascript,现在需要一个全新的 JS 基础架构来进行线程同步和全新的 类 错误。浏览器的设计者 Javascript 一直决定不打开那个盒子。

如果需要,可以改进呈现问题,但会使浏览器代码复杂化。您必须发明一些方法来猜测 运行ning JS 代码何时似乎不再更改 DOM (可能经过了一些毫秒数而没有更多更改),因为您必须避免在每次 DOM 更改时立即重新布局和屏幕绘制。如果浏览器这样做,一些 JS 操作将变得比现在慢 100 倍(100 倍是一个大胆的猜测,但关键是它们会慢很多)。而且,你必须在布局、绘画和 JS DOM 修改之间实现线程同步,这是可行的,但很复杂,大量的工作和浏览器实现错误的沃土。而且,当你在重新布局或重绘的中途并且 JS 线程进行 DOM 修改时,你必须决定要做什么(none 的答案很好)。