众所周知,JavaScript 在所有现代浏览器实现中都是单线程的,但这是在任何标准中指定的还是只是传统上的?假设 JavaScript 始终是单线程的是否完全安全?
这是个好问题。我很想说“测测”。我不能。
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()或打开弹出窗口或任何其他移动焦点而发生。
log in, blur, log out
focus()
alert()
这也可能导致其他事件。例如,添加一个侦听器并在调用取消焦点i.onchange之前在输入中键入一些内容,并且日志顺序是,除了在它所在的 Opera和它所在的 IE 中(甚至更难解释)。focus()``log in, change, blur, log out``log in, blur, log out, change``log in, change, log out, blur
i.onchange
focus()``log in, change, blur, log out``log in, blur, log out, change``log in, change, log out, blur
类似地,在所有浏览器中click()调用提供它的元素会onclick立即调用处理程序(至少这是一致的!)。
click()
onclick
(我在on...这里使用直接事件处理程序属性,但同样的情况也发生在addEventListener和attachEvent。)
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文本区域。
alert
alert in, resize, alert out
您可能认为在模式对话框打开时调整窗口大小是不可能的,但事实并非如此:在 Linux 中,您可以随意调整窗口大小;在 Windows 上,这并不容易,但是您可以通过将屏幕分辨率从较大的屏幕分辨率更改为较小的屏幕分辨率来实现,因为窗口不适合,导致它被调整大小。
你可能会想,好吧,只有resize(可能还有更多类似的scroll)当用户没有与浏览器进行主动交互时才会触发,因为脚本是线程化的。对于单个窗口,您可能是对的。但是,一旦您执行跨窗口脚本,这一切都将付诸东流。对于除 Safari 之外的所有浏览器,当它们中的任何一个忙时会阻止所有窗口/选项卡/框架,您可以从另一个文档的代码与文档交互,在单独的执行线程中运行并导致任何相关的事件处理程序火。
resize
scroll
在脚本仍然线程化的情况下,可以引发您可以生成的事件的地方:
当模式弹出窗口(alert, confirm, prompt)打开时,在除 Opera 之外的所有浏览器中;
confirm
prompt
在showModalDialog支持它的浏览器上;
showModalDialog
“此页面上的脚本可能正忙…”对话框,即使您选择让脚本继续运行,也允许触发和处理调整大小和模糊等事件,即使脚本处于中间一个繁忙的循环,除了在 Opera 中。
不久前,在带有 Sun Java 插件的 IE 中,调用 applet 上的任何方法都可以触发事件并重新输入脚本。这一直是一个对时间敏感的错误,Sun 可能已经修复了它(我当然希望如此)。
可能更多。自从我对此进行测试已经有一段时间了,从那以后浏览器变得越来越复杂。
总之,在大多数用户看来,JavaScript 在大多数情况下都具有严格的事件驱动单线程执行。实际上,它没有这样的东西。