浏览器中的 JS 的执行流程和 NodeJS 中的流程都是遵循事件循环的。理解事件循环的工作方式对于代码优化很重要,有时对于正确的架构也很重要。
事件循环——微任务和宏任务
浏览器中的 JS 的执行流程和 NodeJS 中的流程都是遵循事件循环的。
一、事件循环
定义
事件循环是 JavaScript 引擎在等待任务、执行任务、以及进入休眠等待更多任务,这三个状态之间转换的无限循环。
引擎的一般流程(循环):
- 设置任务
- 引擎处理任务(如果有多个任务,则它们会组成一个队列即“宏任务队列”,按照先进先出的原则来处理任务即先进入的任务会被先执行)
- 所有任务处理完成,引擎进入休眠等待新任务出现(休眠期间几乎不消耗任何CPU资源),然后转到第1步
注意:
- 引擎执行任务时永远不会进行渲染(render)。即使任务执行需要很长时间,它也会等到任务完成之后才会绘制对 DOM 的更改。
- 如果一项任务执行时间过长,则在该任务完成前,浏览器将无法执行后续其它任务。因此,在预设的某个时间段后,浏览器会抛出一个诸如“页面未响应”之类的警告,建议终止这个任务。(这种情况通常发生在有大量复杂计算或导致死循环的程序错误时)
二、拆分CPU过载任务
问题场景:如果 JS 引擎正在执行一个 CPU 过载的任务即该任务需要耗费大量的 CPU 资源和时间。那么在该任务完成前,引擎无法处理其它的DOM相关的任务,例如处理用户事件,这会表现为“网页卡顿很长一段时间,并且无法响应用户的其它交互行为”。这种情况是需要被避免的。
解决方法:这时,我们可以通过将大任务拆分为多个小任务来避免这个问题。每完成一个小任务,就使用setTimeout
(延时参数设置为0)方法来再次安排一个小任务,直到所有小任务都被完成。
示例代码如下:
1 | let i = 0; |
上面的代码块展示了一个耗时很长的大任务,该任务可能会使浏览器显示一个“脚本执行时间过长”的警告。
下面,我们使用setTimeout
方法来拆分这个大任务:
1 | let i = 0; |
现在,JS 引擎可以分批处理每个小任务,并且不影响后续其它任务的执行。原因在于:
- 引擎首先执行(*)的小任务
- 完成一个小任务后,如果该任务不是最后一个小任务,则调用
setTimeout
来安排一个新的小任务 - 执行宏任务队列中的剩余任务,然后重复步骤一、二,直到所有小任务都完成。
可见,关键在于**setTimeout
函数,setTimeout
会在时间到达时,为 JS 引擎安排一个回调函数事件,该事件会被插入到宏任务队列中**。在此之前,如果有其他事件发生,如用户点击事件,则会被先安排入宏任务队列,并按照先进先出的原则,优先执行,从而避免“脚本执行时间过长”、浏览器“挂起(hang)”的问题。
此外注意:多个嵌套的setTimeout
调用在浏览器中的最小延迟为 4ms。因此即使我们设置了延迟为0,但还是至少需要 4ms的延迟。所以如果安排的越早,运行会越快,上述代码可以改写一下:
1 | let i = 0; |
三、宏任务&微任务
除了宏任务(macrotask),还有微任务(microtask)。
微任务仅来自我们的代码:
- 由 Promise 创建的 promise 对象的
.then/catch/finally
的处理程序 await
的幕后(因此 await 是 promise 的另一种形式)- **特殊函数
queueMicrotask(func)
**,它会对func
进行排队,以在微任务队列中执行
规则
每个宏任务之后,引擎会立即执行微任务队列中的所有微任务,然后再执行其他的宏任务或渲染或其它操作。
举例如下:
1 | setTimeout(() => alert('3')); |
对上述代码,引擎的执行顺序是:
- 首先执行同步代码
alert('1')
,所以首先显示“1” - 然后执行 Promise 中的
then
中的回调函数,因为then
是一个微任务,它会在微任务队列中,等一个宏任务执行结束后,会被立即执行,所以第二个显示的是“2” - 最后异步调用
setTimeout
中的回调函数会被从宏任务队列中取出执行,所以最后显示的是“3”
注:微任务会在执行任何其他操作(宏任务或渲染)前被完成,因此它确保了微任务之间的应用程序环境基本相同(即没有新的网络数据、没有DOM更改等)。
四、总结事件循环算法
宏任务队列遵循先进先出原则,首先取出并执行最早的任务
执行微任务队列中所有的微任务:
- 微任务队列也遵循先进先出原则,首先取出并执行最早的微任务
渲染(如果DOM改变)
重复前三个步骤
直到宏任务队列为空,则 JS 引擎休眠等待新任务
五、安排新的宏任务
可以使用零延迟的setTimeout
。它同时还可以将大任务拆分为多个小任务,以便浏览器能够对用户事件作出反应。