众所周知,JavaScript在所有现代浏览器实现中都是单线程的,但这是在任何标准中指定的,还是仅仅是传统?假设JavaScript总是单线程的,这是完全安全的吗
这是个好问题。我想说“是的”。我不能
JavaScript通常被认为有一个对脚本(*)可见的执行线程,因此当输入内联脚本、事件侦听器或超时时,您将保持完全控制,直到您从块或函数结束返回
(*:忽略浏览器是否真的使用一个OS线程实现其JS引擎,或者WebWorkers是否引入了其他有限的执行线程。)
然而,在现实中,这并不完全是真的,以卑鄙的方式
最常见的情况是即时事件。当您的代码导致这些错误时,浏览器会立即触发这些错误:
var l=document.getElementById('log');
var i=document.getElementById('inp');
i、 onblur=函数(){
l、 值+='blur\n';
};
setTimeout(函数(){
l、 值+='登录\n';
l、 焦点();
l、 值+='注销\n';
}, 100);
i、 焦点()
<;textarea id=“log”rows=“20”cols=“40”></textarea>;
<;输入id=“inp”>
导致除IE之外的所有主机上出现登录、模糊、注销。这些事件不仅仅是因为您直接调用了focus()而触发的,还可能是因为您调用了alert(),或打开了弹出窗口,或其他任何移动焦点的操作而触发的
这也可能导致其他事件。例如,添加一个i.onchange侦听器,并在focus()调用解除其焦点之前在输入中键入一些内容,日志顺序为登录、更改、模糊、注销,除了在Opera中,它是登录、模糊、注销、更改和IE(更不明确地说)登录、更改、注销,模糊
类似地,在提供它的元素上调用click(),会在所有浏览器中立即调用onclick处理程序(至少这是一致的!)
(我在这里使用的是…事件处理程序属性上的直接,但在addEventListener和attachEvent中也会发生同样的情况)
还有很多情况下,当代码被线程化时,事件可能会触发,尽管您没有做任何事情来触发它。例如:
var l=document.getElementById('log');
document.getElementById('act')。onclick=function(){
l、 值+='alert in\n';
警惕(‘警惕’);
l、 值+='alert out\n';
};
window.onresize=function(){
l、 值+='resize\n';
};
<;textarea id=“log”rows=“20”cols=“40”></textarea>;
<;按钮id=“act”>;警惕</按钮>
点击alert,您将看到一个模式对话框。在你取消对话之前,不会再执行脚本了,是吗?不。调整主窗口的大小,您将在文本区域中获得警报输入、调整大小、警报输出
您可能认为在模式对话框打开时不可能调整窗口大小,但事实并非如此:在Linux中,您可以随意调整窗口大小;在Windows上,这并不是那么容易,但是你可以通过将屏幕分辨率从一个较大的分辨率更改为一个较小的分辨率来实现这一点,在这个分辨率不适合窗口的情况下,使其调整大小
您可能会认为,当用户由于脚本是线程化的而无法与浏览器进行主动交互时,只有调整大小(可能还有一些更像滚动)才能触发。对于单窗口,你可能是对的。但是,一旦您执行跨窗口脚本编写,所有这些都将立即变成pot。对于Safari以外的所有浏览器(当任何一个浏览器忙时会阻止所有窗口/选项卡/框架),您可以通过另一个文档的代码与文档交互,在单独的执行线程中运行,并引发任何相关事件处理程序
当脚本仍处于线程状态时,可以引发可导致生成的事件的位置:
-
当模式弹出窗口(
警报,确认,提示)在除Opera以外的所有浏览器中打开时 -
在支持showModalDialog的浏览器上进行
showModalDialog -
不久前,对于我来说,在带有Sun Java插件的IE中,调用applet上的任何方法都可以触发事件并重新输入脚本。这一直是一个对时间敏感的bug,Sun可能已经修复了它(我当然希望如此)
-
可能更多。我测试这个已经有一段时间了,浏览器也变得越来越复杂
>p>“此页上的脚本可能很忙…”对话框,即使您选择让脚本继续运行,也允许像大小调整和模糊之类的事件发生,即使脚本在忙循环中,也可以处理,除非在Opera中。
总之,大多数用户在大多数情况下都认为JavaScript具有严格的事件驱动单线程执行。实际上,它没有这样的东西。目前还不清楚这其中有多少只是一个bug,有多少是经过深思熟虑的设计,但如果您正在编写复杂的应用程序,尤其是跨窗口/框架脚本编写的应用程序,它很有可能会咬到您 — 并且以间歇性的、难以调试的方式
如果出现最坏的情况,您可以通过间接处理所有事件响应来解决并发问题。当事件出现时,将其放入队列中,然后在setInterval函数中按顺序处理队列。如果您正在编写一个复杂应用程序要使用的框架,那么这样做可能是一个很好的举措postMessage也有望缓解未来跨文档脚本编写的痛苦