JavaScript可以保证是单线程的吗?

2022-08-29 22:37:03

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


答案 1

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

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

(*:忽略浏览器是否真的使用一个操作系统线程实现其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">

结果出现在除 IE 之外的所有设备上。这些事件不仅因为您直接呼叫而触发,还可能因为您呼叫 或打开弹出窗口或其他任何移动焦点而发生。log in, blur, log outfocus()alert()

这还可能导致其他事件。例如,在调用取消焦点之前,添加一个侦听器并在输入中键入一些内容,日志顺序为,除了在Opera中它是和IE中它是(甚至不那么明显)。i.onchangefocus()log in, change, blur, log outlog in, blur, log out, changelog 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>

点击,你会得到一个模式对话框。在你关闭该对话之前,不再执行脚本,是吗?不。调整主窗口的大小,您将进入文本区域。alertalert in, resize, alert out

您可能认为在模式对话框启动时不可能调整窗口大小,但事实并非如此:在Linux中,您可以根据需要调整窗口大小;在Windows上,这并不容易,但是您可以通过将屏幕分辨率从较大的屏幕分辨率更改为较小的窗口不适合的屏幕分辨率来做到这一点,从而导致其调整大小。

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

在脚本仍处于线程化状态时,可以引发可能导致生成的事件的位置:

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

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

  • “此页面上的脚本可能正忙...”对话框,即使您选择让脚本继续运行,也允许触发和模糊等事件,即使在脚本处于繁忙循环中(Opera 中除外)。

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

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

总而言之,对于大多数用户来说,JavaScript在大多数时候似乎都有一个严格的事件驱动的单线程执行。实际上,它没有这样的东西。目前尚不清楚其中有多少只是一个错误,有多少是经过深思熟虑的设计,但是如果你正在编写复杂的应用程序,特别是跨窗口/框架脚本的应用程序,那么它完全有可能咬你 - 并且以间歇性,难以调试的方式。

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


答案 2

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

除此之外,HTML5已经指定了Web Workers(一种用于多线程javascript代码的显式,标准化的API)将多线程引入基本Javascript的事实基本上是毫无意义的。

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