图解JavaScript事件循环:微任务和宏任务
时间:2023-03-15 08:31:59
科技观察
事件循环:微任务和宏任务JavaScript在浏览器中的执行流程和Node.js中的流程都是基于事件循环的。了解事件循环的工作原理对于代码优化很重要,有时对于正确的架构也很重要。在本章中,我们首先介绍有关事件循环如何工作的理论细节,然后介绍这些知识的实际应用。事件循环事件循环的概念非常简单。这是一个无限循环,在JavaScript引擎等待任务、执行任务和休眠等待更多任务的状态之间转换。引擎的一般算法:1.当有任务时:从第一个进入的任务开始执行。2.休眠,直到出现一个任务,然后转到第1步。这就是上面我们浏览网页时的表格。JavaScript引擎大部分时间什么都不做,它只在脚本/处理程序/事件触发时执行。任务示例:当加载外部脚本functioncount(){for(leti=0;i<1e6;i++){i++;progress.innerHTML=i;}}count();...但我们可能还想在任务执行期间显示一些东西,例如进度条。如果我们使用setTimeout将繁重的任务拆分成多个部分,它们之间的变化将被绘制出来。这看起来更好:
现在div显示的是i值的增长,这是一种进度条。用例3:在事件处理程序中执行某些操作在事件处理程序中,我们可能会决定将某些行为推迟到事件冒泡并在所有级别上处理之后。我们可以通过将该代码包装在零延迟的setTimeout中来做到这一点。在创建自定义事件[1]一章中,我们看到了一个这样的例子:自定义事件menu-open是在setTimeout中调度的,所以它发生在处理完点击事件之后。menu.onclick=function(){//...//用点击菜单项的数据创建自定义事件letcustomEvent=newCustomEvent("menu-open",{bubbles:true});//异步派发(dispatch)自定义事件setTimeout(()=>menu.dispatchEvent(customEvent));};除了本章提到的宏任务(macrotask)外,microtask队列中还有macrotasks和microtasks[2]章提到的microtask。微任务仅来自我们的代码。它们通常由承诺创建:.then/catch/finally处理程序的执行成为微任务。微任务也用于等待的“幕后”,因为它是另一种形式的承诺处理。还有一个特殊的函数queueMicrotask(func)将func排入微任务队列中执行。在每个宏任务之后,引擎会在执行其他宏任务、渲染或其他任何任务之前立即执行微任务队列中的所有任务。例如,看看这个例子:setTimeout(()=>alert("timeout"));Promise.resolve().then(()=>alert("promise"));alert("code");这里的执行顺序是什么?首先显示代码,因为它是常规同步调用。promise排在第二位,因为then通过微任务队列并在当前代码之后执行。超时显示在最后,因为它是一个宏任务。更详细的事件循环图如下(顺序是从上到下,即:先是脚本,然后是微任务、渲染等):微任务会执行任何其他的事件处理,或者渲染,或者任何之前完成的其他宏任务。这很重要,因为它确保微任务之间的应用程序环境本质上是相同的(没有鼠标坐标变化,没有新的网络数据等)。如果我们希望一个函数异步执行(在当前代码之后),但在呈现更改或处理新事件之前,那么我们可以使用queueMicrotask来调度它。下面是一个与上一个类似的带有“计数进度条”的示例,但它使用queueMicrotask而不是setTimeout。你可以看到它是在最后渲染的。就像写同步代码一样:
更详细地总结了事件循环算法(虽然类似于规范[3]的ratio仍然是简化的:1.从宏任务队列(例如“脚本”)中出队并执行最早的任务。2.执行所有微任务:出列并执行最早的微任务。当微任务队列不为空时:3.执行渲染,如果有的话。4.如果宏任务队列为空,则休眠直到出现宏任务。5.转到步骤1。安排一个新的宏任务:setTimeout(f)零延迟。它可用于将繁重的计算任务拆分为多个部分,使浏览器能够对用户事件做出反应,并显示任务各部分之间的任务进度。此外,它还用于事件处理程序中,以在事件完全处理(冒泡完成)后安排一个动作。安排一个新的微任务:使用queueMicrotask(f)。Promise处理程序也通过微任务队列。微任务之间不处理UI或网络事件:它们一个接一个地立即执行。因此,我们可以使用queueMicrotask异步执行函数,同时保持环境状态一致。WebWorkers:对于不应阻塞事件循环的耗时繁重的计算任务,我们可以使用WebWorkers[4]。这就是您在另一个并行线程中运行代码的方式。WebWorkers可以与主线程交换消息,但它们有自己的变量和事件循环。WebWorker无法访问DOM,因此,它们对于同时使用多个CPU内核的计算很有用。参考资料[1]创建自定义事件:https://zh.javascript.info/dispatch-events[2]微任务队列:https://zh.javascript.info/microtask-queue[3]规范:https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model[4]WebWorkers:https://html.spec.whatwg.org/multipage/workers.html[5]React推荐者官方文档,和MDN一起的JavaScript学习教程:https://zh-hans.reactjs.org/docs/getting-started.html#javascript-resources二维码关注。转载本文请联系TechnicalTalk公众号。