引言
对于从事前端的同学来说,应该对浏览器中的 Event Loop
有所了解。这个了解的过程,可能是自己总结,也可能是通过网上的各种讲解。可能,对于前者而言能总结出 Event Loop
,说明对它的概念已通透于心。但是,对于后者而言,很可能跌入理解的误区,因为网上有很多种 Event Loop
的讲解的版本,各自有描述的重点,并不是能很好地表达 Event Loop
的执行流程(我曾经也是这样理解…)。
所以,今天,我们就来详细地认识一下浏览器中的 Event Loop
。
一、Event Loop 定义
首先,我们来回忆一下,Event Loop
的意义何在?众所周知,浏览器的 JS
引擎线程为单线程。那么,为了防止代码阻塞的情况出现,所以设置了 Event Loop
这种事件循环机制。
往大地讲,它是将一些耗时可能会很长的代码通过异步执行,例如 XMLHTTPRequest
请求过程。往小地讲,对于这个异步的过程,通常被称作任务队列或消息队列,即 JS
引擎在解析 JS
代码的时候,会将异步的代码运行结果(通常是回调)放在任务队列中,而任务队列又被分为 marco task
(宏任务)和 micro task
(微任务)(它们各自由相应的队列维护)。而在每次宏任务执行后(执行一个宏任务),都会询问微任务队列,然后按照 FIFO 先进先出的原则清空微任务队列。
常见的宏任务有:
- 主代码块(即 JS 引擎解析解析同步代码的过程)
- 页面渲染
- setTimeout
- setInterval
- setImmediate
常见的微任务有:
- Promise.then
- MutationObserver
PS:由于只是针对浏览器的 Event Loop,就不提 Node 的 process.nextick 之类的了~
通过,上面的概念性的理解,我想大家可能对 Event Loop
如何执行还是有点迷糊,那么接下来,我们就重点通过一个 Demo
来深入了解一下 Event Loop
的运行机制。
二、Event Loop 运行机制
首先,我们先看一段简单的代码:
console.log(1) // {1}
Promise.resolve().then(() => { // {2}
console.log(3)
Promise.resolve().then(() => {
console.log(4)
})
})
setTimoue(() => { // {3}
console.log(5)
Promise.resolve().then(() => {
console.log(6)
})
})
console.log(2) // {4}
最终会打印输出 1 2 3 4 5 6,这是为什么呢?
1.首先,JS
引擎先解析 {1} 时,由于是同步的,所以直接打印出 1
2.然后,解析 {2},由于 Promise.then
是微任务(记作任务 A),所以会将它的运行结果放入微任务队列,那么此时任务队列为:
3.接下来,解析 {3},由于 setTimeout
是宏任务(记作任务 B),所以会讲它的运行结果放入宏任务队列,那么此时任务队列为:
4.然后,再解析 {4},由于是同步的,所以直接打印出 2,此时任务队列不变。
5.此时主代码块执行完了(即宏任务执行完一次),此时去询问微任务队列,清空微任务队列(此时微任务队列刚好只有任务 A),执行完之后的任务队列为:
并且,此时需要注意的是,在任务 A 的执行过程中,不仅同步打印了 3,而且还生成了一个微任务,即这个微任务也会进入微任务队列,然后出队,即输出 4,这样微任务队列才被清空。
6.清空后微任务队列,会去询问宏任务队列,即执行任务 B(回调),同步打印 5,并且生成一个微任务(记作任务C),讲该微任务的运行结果放到微任务队列中,此时任务队列为:
7.同样地,在执行完一次宏任务后,会清空微任务队列,即执行任务 C,打印出 6。而自此,微任务队列和宏任务队列都为空,Event Loop
就运行结束了。
总结
不知道大家最后弄清楚 Event Loop
了没,记忆的过程中有很重要的两点,首先微任务和宏任务是基于队列进行管理的,它遵循 FIFO
的原则。并且在每一次宏任务执行一次结束后,都会清空微任务队列。只要,大家明白这两点,然后结合上面的例子理解,我想浏览器中的 Event Loop
对你来说应该就是小菜一碟。