我一直在阅读这篇文章http://ejohn.org/blog/how-javascript-timers-work/以及setTimeout
和setInterval
以及其他异步任务(例如按钮点击)如何让我感到困惑。
我知道JS是单线程的,这意味着,AFAIK,所有回调函数(a.k.a.事件处理程序)将按顺序排队并执行。但是,请看下面我从上面链接的文章中获取的图片:
每个块代表一些工作,并且 - 大约10ms - 计时器被触发。我知道它的回调函数被放在队列中以便以后执行,但是为什么在执行某些事情时可以调用该事件呢?
是因为setTimeout()
开始使用单独的线程来计算内部时间并触发其完成事件吗?
请注意,我不是在谈论它的回调执行;相反,我试图了解setTimeout
如何计算时间并解雇它。我知道它的回调函数不会在它给定的时间参数之前调用,但可能会在稍后调用,但这是因为它的回调排队等待运行时间找到时间来检查队列中是否有任何内容执行的时间。
类似于这个问题,为什么浏览器会接受新的点击进行注册 - 让我们说 - 在用户点击时,一个循环正在幕后工作?
如果你说浏览器为不同的东西维护不同的线程,那么我们可以在单线程浏览器中调用JS吗?
答案 0 :(得分:18)
JavaScript是单线程的,这意味着两个JavaScript不会同时执行(除非使用worker,但每个worker也是单线程的)。但是,当谈到环境的API时,JavaScript并不是唯一可行的方法。 setTimeout
,setInterval
和addEventListener
等函数本机实现(或者至少在单线程JavaScript之外),并且它们的回调由环境触发,例如浏览器。将这些回调放入队列的环境是由单线程JavaScript引擎执行的。这就是浏览器能够接受触发的新点击事件的方式,即使它仍在执行其他一些JavaScript代码。这就是JavaScript被视为事件驱动的原因。
答案 1 :(得分:2)
目前(Ecmascript 6),您永远不必担心功能中途的状态发生变化。
然而,许多事情确实在后台运行,并且它们改变了自己的孤立环境,后来可能将其作为数据块发送到主环境。 XMLHttpRequest回调队列,计时器和Web工作者都这样做。它们都可能同时运行(在多个CPU中),但它们都以顺序方式与主环境通信。
答案 2 :(得分:1)
没有必要使用其他线程来解释此行为。当计时器记录添加到队列中时,它只是坐在那里。如果事件循环再次获得控制权,它只会检查是否存在时间已到的计划任务。
由于操作系统或执行环境已经提供了全局时间,因此无需额外的线程来保持全局时间。
以这个简单的PhantomJS脚本为例:
function longRunningTask() {
for(var i = 0; i < 100000; i++) {
var s = "",
s2 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
for(var j = 0; j < 1000; j++) {
s += s2;
}
}
}
var start = new Date().getTime();
console.log("begin");
setTimeout(function(){
console.log("end timer 1s " + (new Date().getTime() - start));
}, 1000);
setTimeout(function(){
console.log("end timer 10s " + (new Date().getTime() - start));
}, 10000);
longRunningTask();
console.log("end longRunningTask " + (new Date().getTime() - start));
setTimeout(function(){
console.log("EXIT");
phantom.exit();
}, 11000);
产生以下输出:
begin end longRunningTask 5025 end timer 1s 5029 end timer 10s 10001 EXIT
一秒计时器仅在控件返回事件循环时触发。