本文已参与「新人创作礼」活动,一起开启掘金创作之路。
什么是事件循环?
对于事件循环的官方定义是:事件循环(或者主循环,如果是中央控制方面的话)是程序内部的一种结构,它在初始事件之后控制和分派事件。
单线程
js是单线程的一门语言,即在同一时间只能做同一件事情;这样作为一门浏览器脚本语言就可以与用户互动以及操作DOM
任务列表
作为一门单线程的语言,那么js与生俱来的属性就是所有需要完成的任务都要一个一个的执行,只有当前面的一个任务结束才会执行下一个任务。并且如果前面一个任务耗时很长,后面的任务就不得不等待当前任务执行结束才能开始执行,就比如当前任务是进行IO设备的输入输出,我们知道如果让一个手指不灵活的老人来打字,这中间可能会浪费很多输入的时间,而如果因为等待输入这个任务执行完毕再进行后续的其他任务,那么就会造成CPU不合理的空闲。于是js的设计者意识到先挂起等待中的任务,先运行后面的任务,等到IO设备返回结果再往下执行。
于是任务就分为了两种:
-
同步任务
-
异步任务(分为两种)
-
- 微任务队列:包含process.nextTick, promise.then, MutationObserver
-
- 宏任务队列:包含script, setTimeout, setInterval, setImmediate, I/O, UI-rendering
这些任务的执行顺序:
- 首先执行同步代码,这属于宏任务
- 当执行完所有的同步代码后,执行栈为空,检查是否有异步代码要执行
- 执行微任务
- 执行完微任务后,有必要的情况下会渲染页面
- 开启下一轮event-loop,执行宏任务中的代码
代码在执行的过程中,遇到异步代码会将异步代码用队列挂起,等同步代码执行完成后再来执行异步代码。 下面用这个例子来演示同步任务与异步任务的执行顺序:
console.log('start');
setTimeout(() => {
console.log('setTimeout');
}, 0)
new Promise(resolve => {
console.log('Promise');
resolve()
})
.then(() => {
console.log('promise1');
})
.then(() => {
console.log('promise2');
})
console.log('end');
//start
//Promise
//end
//promise1
//promise2
//setTimeout
当V8引擎大哥看到这段代码的时候,先执行同步任务即先打印start,然后往下看到setTimeout函数,这是异步代码中的宏任务所以先挂起;往后又碰到了promise(),看看异步任务里面没有它的身影,所以它就属于同步代码,打印出Promise;然后连续碰到两个promise.then(),它们时宏任务中的微任务队列,也先挂起;于是到了最后执行console.log('end'),打印出end;现在就把同步代码执行完毕了,再执行异步代码,这就走到了我们所说的任务执行顺序的第三步:执行微任务,所以先打印出promise1再打印出promise2;最后再执行宏任务setTimeout,打印出setTimeout,于是就有了这样子的结果。