众所周知,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侦听器,然后在focus()调用将其取消焦点之前在输入中键入某些内容,并且日志顺序为log in, change, blur, log out,除了Opera所在的位置log in, blur, log out, change和IE所在的位置(甚至更少)log in, change, log out, blur。
i.onchange
log in, change, blur, log out
log in, blur, log out, change
log in, change, log out, blur
同样,调用click()提供它的元素会onclick在所有浏览器中立即调用处理程序(至少这是一致的!)。
click()
onclick
(我在这里使用直接on...事件处理程序属性,但addEventListenerand 也会发生同样的情况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)是开放的,在所有的浏览器,但歌剧;
confirm
prompt
在showModalDialog支持它的浏览器中;
showModalDialog
即使您选择让脚本继续运行,“此页面上的脚本可能正忙于…”对话框也将允许触发大小调整和模糊等事件,即使该脚本位于脚本中间,也可以对其进行处理。忙循环,Opera除外。
对我来说,前一段时间,在带有Sun Java Plugin的IE中,调用applet上的任何方法都可以触发事件并重新输入脚本。自始至终,这始终是一个对时间敏感的错误,Sun可能已经对此进行了修复(我当然希望如此)。
可能更多。自从我测试了这已经有一段时间了,浏览器也因此变得越来越复杂。
总之,大多数时候,JavaScript对大多数用户来说似乎具有严格的事件驱动的单线程执行。实际上,它没有这样的东西。尚不清楚其中有多少只是一个错误,有多少是经过深思熟虑的设计,但是如果您要编写复杂的应用程序,尤其是跨窗口/框架脚本的应用程序,那么它很有可能会咬住您- 并且断断续续地,难以调试的方式。
如果情况变得最糟,则可以通过间接所有事件响应来解决并发问题。当事件进入时,将其放入队列中,然后在setInterval函数中按顺序处理该队列。如果您要编写打算供复杂应用程序使用的框架,那么这样做可能是一个不错的选择。postMessage希望将来也能缓解跨文档脚本编写的痛苦。
setInterval
postMessage