这是个好问题。我很想说“是”。我不能。
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">
在除 IE 之外的所有设备上都生成 log in, blur, log out。这些事件不只是因为您直接调用了focus() 而触发,它们可能会因为您调用alert()、打开一个弹出窗口或任何其他移动焦点而发生。
这也可能导致其他事件。例如添加一个i.onchange 侦听器并在focus() 调用取消焦点之前在输入中键入一些内容,并且日志顺序是log in, change, blur, log out,除了在Opera 中它是log in, blur, log out, change 和IE 中它是(甚至更难解释)@ 987654330@.
类似地在一个元素上调用click(),它会立即在所有浏览器中调用onclick 处理程序(至少这是一致的!)。
(我在这里使用直接的on... 事件处理程序属性,但addEventListener 和attachEvent 也是如此。)
还有很多情况下,当您的代码被线程化时,事件可能会触发,尽管您没有做任何事情 来引发它。一个例子:
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 之外的所有浏览器,当它们中的任何一个忙时会阻止所有窗口/选项卡/框架,您可以从另一个文档的代码与文档交互,在单独的执行线程中运行并导致任何相关的事件处理程序火。
在脚本仍然线程化时可以引发您可能导致生成的事件的地方:
当模式弹出窗口(@987654342@、confirm、prompt)在除 Opera 之外的所有浏览器中打开时;
在支持 showModalDialog 的浏览器上;
“此页面上的脚本可能正忙...”对话框,即使您选择让脚本继续运行,也允许触发和处理调整大小和模糊等事件,即使脚本正在执行处于繁忙循环的中间,Opera 除外。
对我来说,不久前,在带有 Sun Java 插件的 IE 中,调用小程序上的任何方法都可以触发事件并重新输入脚本。这一直是一个对时间敏感的错误,自那以后 Sun 可能已经修复了它(我当然希望如此)。
可能更多。我已经有一段时间没有测试过了,从那以后浏览器变得越来越复杂。
总之,在大多数用户看来,JavaScript 在大多数情况下都具有严格的事件驱动单线程执行。实际上,它没有这样的东西。目前尚不清楚其中有多少只是一个错误,有多少是经过深思熟虑的设计,但如果你正在编写复杂的应用程序,尤其是跨窗口/框架脚本的应用程序,它很有可能会咬到你 - 并且间歇性地,难以调试的方式。
如果最坏的情况发生,您可以通过间接所有事件响应来解决并发问题。当事件进入时,将其放入队列中,稍后在 setInterval 函数中按顺序处理队列。如果您正在编写一个打算供复杂应用程序使用的框架,那么这样做可能是一个不错的举措。 postMessage 也有望在未来缓解跨文档脚本的痛苦。