[翻译][JavaScript] JavaScript 是不是一定是单线程的?
原问题
Is JavaScript guaranteed to be single-threaded?
众所周知,JavaScript 在所有现代的浏览器实现中都是单线程的,但这是在任何标准中指定的,还是仅仅是传统的? 完全可以假设 JavaScript 总是单线程的吗?
高赞回答
这是个好问题,我很想说“是的” ,但我不能。
JavaScript 通常被认为只有一个对脚本可见的执行线程(*) ,因此当输入内嵌脚本、事件侦听器或超时时,你完全可以控制它,直到你从块或函数的末尾返回。
(*:忽略浏览器是否真的使用一个操作系统线程实现它们的 JS 引擎,或者 WebWorkers 是否引入了其他有限的执行线程。)
最常见的情况是即时事件。当你的代码执行某些操作运行它们时,浏览器会立即将其触发:
<!DOCTYPE html>
<html>
<head>
<title>Javascript </title>
</head>
<body>
<div>
<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">
</div>
<script type="text/javascript">
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();
</script>
</body>
</html>
除了IE浏览器之外的浏览器都会显示结果:log in
,blur
,log out
。这些事件不仅仅是因为直接调用 focus()而触发,它们还可能因为调用alert()、打开弹出窗口或任何其他移动焦点的操作而发生。
这也可能导致其他事件。例如,在focus()调用取消焦点之前,添加一个i.onchange监听器,并在输入中键入一些内容,日志顺序是log in
、change
、blur
、'log out',但是在 Opera浏览器中是log in
、blur
、log out
、change
,在IE中是log in
、change
、log out
、'blur'。
类似地,在所有浏览器中,对提供它的元素调用 click ()会立即调用 onclick 处理程序(至少这是一致的!)。
还有一些情况下,当代码被线程化时,事件可能会触发,尽管您没有做任何事情来激发它。 举个例子:
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系统)
你可能会认为调整窗口的大小是不可能的,但事实并非如此: 在 Linux 中,你可以随心所欲地调整窗口的大小; 在 Windows 中就不那么容易了,但是你可以通过改变屏幕分辨率从一个较大的调整到一个不适合窗口的较小的调整,导致窗口调整大小。【笔者注】:在windows系统的chrome浏览器和firefox浏览器中,顺序是alert in
, alert out
, resize
。
您可能认为,当用户与浏览器没有活动交互时,只有调整大小(可能还有一些类似滚动的操作)才能触发,因为脚本是线程化的。对于单一窗口,你可能是对的。但是,一旦您开始编写跨窗口脚本,所有这些都将付诸东流。 对于除 Safari 以外的所有浏览器,当任何一个窗口 / 选项卡 / 框架忙时,都会阻塞它们,您可以与另一个文档的代码中的一个文档进行交互,该文档运行在一个单独的执行线程中,并导致任何相关的事件处理程序启动。
Places where events that you can cause to be generated can be raised whilst script is still threaded:
-
when the modal popups (alert, confirm, prompt) are open, in all browsers but Opera;
-
during showModalDialog on browsers that support it;
-
the “A script on this page may be busy...” dialogue box, even if you choose to let the script continue to run, allows events like resize and blur to fire and be handled even whilst the script is in the middle of a busy-loop, except in Opera.
-
a while ago for me, in IE with the Sun Java Plugin, calling any method on an applet could allow events to fire and script to be re-entered. This was always a timing-sensitive bug, and it's possible Sun have fixed it since (I certainly hope so).
-
probably more. It's been a while since I tested this and browsers have gained complexity since.
In summary, JavaScript appears to most users, most of the time, to have a strict event-driven single thread of execution. In reality, it has no such thing. It is not clear how much of this is simply a bug and how much deliberate design, but if you're writing complex applications, especially cross-window/frame-scripting ones, there is every chance it could bite you — and in intermittent, hard-to-debug ways.
If the worst comes to the worst, you can solve concurrency problems by indirecting all event responses. When an event comes in, drop it in a queue and deal with the queue in order later, in a setInterval function. If you are writing a framework that you intend to be used by complex applications, doing this could be a good move. postMessage will also hopefully soothe the pain of cross-document scripting in the future.