javascript中的Event Loop

之前看了JavaScript 运行机制详解:再谈Event Loop的文章,记录一下自己对js的Event Loop的理解。


单线程的JS

之前做.NET很多时候需要进行异步线程编程,以及一些异步资源调用处理的方面的内容,但是JS中并不存在这些问题,因为JavaScript语言的一大特点就是单线程,当然这是一开始对javascript语言的设计,DOM操作和很多语言一样都需要在主线程上进行操作。当然现在也有Web Worker可以实现js的多线程编程,这个有机会再说。

如何进行异步

如果JS是一个单线程的语言那么如何进行异步操作的呢?其实主要就是靠一个东西那就是任务队列,在js中存在两种任务,一种是同步任务,一种是异步任务。同步任务会在出现成上进行执行,而异步任务会发送给任务队列,主进程便不会再管异步任务了,如果异步任务执行完成,那么他会在任务队列中放置一个事件,而主线程执行完成时,便会读取任务队列上的事件,进而去执行异步任务的回调函数。

Event Loop

那么知道了如何执行异步任务那么什么是Event Loop呢?Loop是循环的一次,那么我看看JS的Event Loop是怎样的循环。

  1. 同步任务放入主线程上进行执行
  2. 异步任务放入任务队列进行执行
  3. 异步任务执行完毕后任务队列添加事件
  4. 主线程执行完成后获取任务队列事件,将事件对应任务放入主线程执行

这么一个从主线程到异步队列在回到主线程的过程就叫做Event Loop,更准确的来说Event Loop是JS对异步事件的处理执行机制。

如果同步任务执行过程过长,那么也会会影响到对异步任务的读取,比如我们看下面的例子:

1
2
3
4
5
6
7
8
9
setInterval(function(){
console.log('one second is gone');
},1000)
while(true){
for(var i =0;i<1000;i++){
console.log(i)
}
}

上面这个例子中,我们设想的结果是,一直从1-1000进行重复的打印,每1秒钟打印一次one second is gone字符,但是实际情况呢?那就是one second is gone字符不会被打印出来,因为while中是一个死循环,在执行同步任务阶段已经被锁死了,他就不会去请求任务队列,所以one second is gone也无法打印。

定时器

定时器是最常用的一步任务之一了,它主要有setTimeout()和setInterval()这两个函数。

我们来引用JavaScript 运行机制详解:再谈Event Loop中的定时器例子来看那看一下。

1
2
3
console.log(1);
setTimeout(function(){console.log(2);},1000);
console.log(3);

好的,这个会输出1 3 2,因为setTimeout()将任务加入了任务队列,在1秒钟后再,加载入了主线程去执行了这行代码。

1
2
setTimeout(function(){console.log(1);}, 0);
console.log(2);

我们也常写这样的代码,这回输出多少?结果是2 1
虽然我们设置了setTimeout延迟事件是0,看起来是立即执行,但是他依然会加入任务队列中,所以一定会在主线程中代码执行完后在进行执行。其次setTimeout参数实际无法设置真正的0,

HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。

实际setTimeout也无法保证会在对应的事件后进行执行,就像最开始的例子一样,setTimeout加入任务队列,只有主线程执行完了之后才会去读取任务队列,遇到setTimeout时会判断事件时候应该执行,但是我们无法保证主线程上的任务执行时间。


理解了Event Loop才能理解JS对异步任务执行的处理,包括像很多类似nextTick的处理。