超详细JS的事件循环机制
0 条评论原文地址:https://mp.weixin.qq.com/s/G2L_9kj8ST0_HPG7yxd2lw
本题是面试常见题目,如果你知道了以下这2点,你可以关闭这个页面了。
- async/await 执行顺序注意点是什么
- 事件循环机制整体流程是什么
JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
macro-task大概包括:
- script(整体代码)
- setTimeout
- setInterval
- setImmediate
- I/O
- UI render
micro-task大概包括:
- process.nextTick
- Promise
- Async/Await(实际就是promise)
- MutationObserver(html5新特性)
整体执行,我画了一个流程图:
总的结论就是,执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。举个例子:
结合流程图理解,答案输出为:async2 end => Promise => async1 end => promise1 => promise2 => setTimeout
但是,对于async/await ,我们有个细节还要处理一下。如下:
本题回答亮点
async/await执行顺序注意
我们知道async
隐式返回 Promise 作为结果的函数,那么可以简单理解为,await后面的函数执行完毕时,await会产生一个微任务(Promise.then是微任务)。但是我们要注意这个微任务产生的时机,它是执行完await之后,直接跳出async函数,执行其他代码(此处就是协程的运作,A暂停执行,控制权交给B)。其他代码执行完毕后,再回到async函数去执行剩下的代码,然后把await后面的代码注册到微任务队列当中。我们来看个例子:
1 | console.log('script start') |
分析这段代码:
- 执行代码,输出
script start
。 - 执行async1(),会调用async2(),然后输出
async2 end
,此时将会保留async1函数的上下文,然后跳出async1函数。 - 遇到setTimeout,产生一个宏任务
- 执行Promise,输出
Promise
。遇到then,产生第一个微任务 - 继续执行代码,输出
script end
- 代码逻辑执行完毕(当前宏任务执行完毕),开始执行当前宏任务产生的微任务队列,输出
promise1
,该微任务遇到then,产生一个新的微任务 - 执行产生的微任务,输出
promise2
,当前微任务队列执行完毕。执行权回到async1 - 执行await,实际上会产生一个promise返回,即
1 | let promise_ = new Promise((resolve,reject){ resolve(undefined)}) |
执行完成,执行await后面的语句,输出async1 end
- 最后,执行下一个宏任务,即执行setTimeout,输出
setTimeout
注意
新版的chrome浏览器中不是如上打印的,因为chrome优化了,await变得更快了,输出为:
1 | // script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout |
但是这种做法其实是违法了规范的,当然规范也是可以更改的,这是 V8 团队的一个 PR ,目前新版打印已经修改。知乎上也有相关讨论,可以看看 https://www.zhihu.com/question/268007969
所以我们可以分2种情况来理解:
如果await 后面直接跟的为一个变量,比如:await 1;这种情况的话相当于直接把await后面的代码注册为一个微任务,可以简单理解为promise.then(await下面的代码)。然后跳出async1函数,执行其他代码,当遇到promise函数的时候,会注册promise.then()函数到微任务队列,注意此时微任务队列里面已经存在await后面的微任务。所以这种情况会先执行await后面的代码(async1 end),再执行async1函数后面注册的微任务代码(promise1,promise2)。
如果await后面跟的是一个异步函数的调用,比如上面的代码,将代码改成这样:
1 | console.log('script start') |
输出为:
1 | // script start => async2 end => Promise => script end => => promise1 => promise2 => async1 end setTimeout |
此时执行完awit并不先把await后面的代码注册到微任务队列中去,而是执行完await之后,直接跳出async1函数,执行其他代码。然后遇到promise的时候,把promise.then注册为微任务。其他代码执行完毕后,需要回到async1函数去执行剩下的代码,然后把await后面的代码注册到微任务队列当中,注意此时微任务队列中是有之前注册的微任务的。所以这种情况会先执行async1函数之外的微任务(promise1,promise2),然后才执行async1内注册的微任务(async1 end).
可以理解为,这种情况下,await 后面的代码会在本轮循环的最后被执行.
答题技巧总结
答本题的时候,可以按照以下思路去答:
- 先说基本知识点,宏任务、微任务有哪些
- 说事件循环机制过程,边说边画图出来
- 说async/await执行顺序注意,可以把 chrome 的优化,做法其实是违法了规范的,V8 团队的PR这些自信点说出来,显得你很好学,理解得很详细,很透彻。
- 把node的事件循环也说一下。(本文由于篇幅限制先不说这部分,下篇会讲,敬请期待)
综合下来,就会加分啦,嘿嘿~
- 本文链接:https://xuehuayu.cn/article/34474.html
- 版权声明:① 标为原创的文章为博主原创,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接。② 标为转载的文章来自网络,已标明出处,侵删。