JavaScript 是否保证是单线程的?

新手上路,请多包涵

众所周知,JavaScript 在所有现代浏览器实现中都是单线程的,但这是在任何标准中指定的还是只是传统上的?假设 JavaScript 总是单线程的是完全安全的吗?

原文由 Egor Pavlikhin 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 598
2 个回答

这是个好问题。我很想说“是”。我不能。

JavaScript 通常被认为具有对脚本 (*) 可见的单个执行线程,因此当您的内联脚本、事件侦听器或超时被输入时,您将保持完全控制,直到您从块或函数的末尾返回。

(*:忽略浏览器是否真正使用一个 OS 线程实现其 JS 引擎的问题,或者 WebWorkers 是否引入了其他有限的执行线程。)

然而,实际上这 _并非完全正确_,以偷偷摸摸的令人讨厌的方式。

最常见的情况是即时事件。当您的代码执行某些操作导致它们时,浏览器会立即触发它们:

 var l= document.getElementById('log');
var i= document.getElementById('inp');
i.onblur= function() {
    l.value+= 'blur\n';
};
setTimeout(function() {
    l.value+= 'log in\n';
    l.focus();
    l.value+= 'log out\n';
}, 100);
i.focus();
 <textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">

log in, blur, log out 上除 IE 之外的所有结果。这些事件不仅会因为您直接调用 focus() 而触发,它们可能会因为您调用 alert() 或打开弹出窗口或任何其他移动焦点的原因而发生。

这也可能导致其他事件。例如,添加一个 i.onchange 侦听器并在 focus() 调用之前的输入中键入一些内容 --- 调用使其失去焦点,并且日志顺序为 log in, change, blur, log out 中的除外 log in, blur, log out, change 在 Opera 中 --- 和 IE 它所在的位置(甚至更不明确) log in, change, log out, blur

类似地,在提供它的元素上调用 click() onclick 处理程序(至少这是一致的!)。

(我在这里使用的是直接的 on... 事件处理程序属性,但是 addEventListenerattachEvent 也是如此。)

还有很多情况下,尽管您 没有采取任何 措施来触发事件,但您的代码被线程化时事件可能会触发。一个例子:

 var l= document.getElementById('log');
document.getElementById('act').onclick= function() {
    l.value+= 'alert in\n';
    alert('alert!');
    l.value+= 'alert out\n';
};
window.onresize= function() {
    l.value+= 'resize\n';
};
 <textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>

点击 alert 你会得到一个模态对话框。除非您关闭该对话,否则不会执行更多脚本,是吗?没有。调整主窗口的大小,您将在文本区域中得到 alert in, resize, alert out

您可能认为在模式对话框打开时无法调整窗口大小,但事实并非如此:在 Linux 中,您可以随意调整窗口大小;在 Windows 上,这不是那么容易,但是您可以通过将屏幕分辨率从较大的分辨率更改为较小的窗口不适合的分辨率来实现,从而调整其大小。

您可能会想,只有 resize (可能还有一些类似 scroll )会在用户没有与浏览器进行主动交互时触发,因为脚本是线程化的。对于单个窗口,您可能是对的。但是,一旦您进行跨窗口脚本编写,这一切都会变得糟糕。对于除 Safari 以外的所有浏览器,它会在任何一个窗口/选项卡/框架繁忙时阻塞所有窗口/选项卡/框架,您可以从另一个文档的代码与一个文档进行交互,在单独的执行线程中运行并导致任何相关的事件处理程序火。

在脚本仍在线程化时可以引发您可能导致生成的事件的地方:

  • 当模式弹出窗口( alertconfirmprompt )在除Opera之外的所有浏览器中打开时;

  • showModalDialog 在支持它的浏览器上;

  • “此页面上的脚本可能正忙…”对话框,即使您选择让脚本继续运行,也允许触发和处理调整大小和模糊等事件,即使脚本处于运行过程中也是如此忙循环,Opera 除外。

  • 对我来说,不久前,在带有 Sun Java 插件的 IE 中,调用小程序上的任何方法都可以触发事件并重新输入脚本。这始终是一个对时间敏感的错误,并且 Sun 可能已经修复了它(我当然希望如此)。

  • 可能更多。自从我测试这个以来已经有一段时间了,从那以后浏览器变得越来越复杂。

总而言之,对于大多数用户而言,JavaScript 在大多数情况下似乎具有严格的事件驱动的单线程执行。实际上,它没有这样的东西。目前尚不清楚其中有多少只是一个错误以及有多少是故意设计的,但如果你正在编写复杂的应用程序,尤其是跨窗口/框架脚本的应用程序,它很有可能会咬你一口——而且是间歇性的,难以调试的方法。

如果发生最坏的情况,您可以通过间接所有事件响应来解决并发问题。当事件到来时,将其放入队列中,稍后在 setInterval 函数中按顺序处理队列。如果您正在编写一个您打算供复杂应用程序使用的框架,那么这样做可能是一个很好的举措。 postMessage 也有望缓解未来跨文档脚本的痛苦。

原文由 bobince 发布,翻译遵循 CC BY-SA 4.0 许可协议

我会说是 - 因为如果浏览器的 javascript 引擎异步运行它,几乎所有现有的(至少所有非平凡的)javascript 代码都会中断。

再加上 HTML5 已经指定了 Web Workers (用于多线程 javascript 代码的显式标准化 API)这一事实,将多线程引入基本 Javascript 几乎毫无意义。

其他评论者请注意: 尽管 setTimeout/setInterval 、HTTP 请求加载事件 (XHR) 和 UI 事件(点击、焦点等)提供了多线程的粗略印象 - 它们仍然全部执行沿着单个时间线——一次一个——所以即使我们事先不知道它们的执行顺序,也无需担心在事件处理程序、定时函数或 XHR 回调执行期间外部条件发生变化。)

原文由 Már Örlygsson 发布,翻译遵循 CC BY-SA 2.5 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏