你真的了解宏任务与微任务的执行顺序吗?你确定是微任务先执行吗?

215 阅读4分钟

前言

我在阅读掘金的时候,看到热榜第一的文章,于是就点开看了看:我入职了

在这篇文章中,作者遇到了这道很经典的面试题:讲讲你对事件循环的理解

作者自信的回答:宏任务先执行

面试官回答到,您可以先回去查查资料

我也在思考?

咦不对呀?我记得是执行宏任务队列前先清空微任务呀。难道是我记错了吗。于是我马上来到我的博客翻了翻笔记

Javascript高级 - 续 - 鲸落 (xiaojunnan.cn)

  • 在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行

    • 也就是宏任务执行之前,必须保证微任务队列是空的;
    • 如果不为空,那么就优先执行微任务队列中的任务(回调)

但是我当时并没有注意到,就在这个笔记的上方纪录了这样一句话:main script中的代码优先执行(编写的顶层script代码)

于是我继续往下看,看到评论区也是吵的不可开交

于是为了一探究竟,我自己开始查找资料来验证到底是怎么回事

什么是事件循环

事件循环(Event Loop)是单线程的JavaScript在处理异步事件时进行的一种循环过程,具体来讲,对于异步事件它会先加入到事件队列中挂起,等主线程空闲时会去执行事件队列(Event Queue)中的事件。如此反复循环。事件循环的设计使得 JavaScript 可以在单线程下处理异步操作,避免了阻塞的情况,保证了程序的响应性和流畅性。

宏任务和微任务

在 JavaScript中任务分为同步与异步任务,其中异步任务又分为两种:宏任务(Macro Task)和 微任务(Micro Task)。

常见宏任务:

  • script标签中的代码
  • setTimeout
  • setInterval
  • setImmediate(Node.js)
  • IO
  • UI 渲染
  • MessageChannel

常见微任务:

  • Promise.then(非 new Promise)
  • async
  • await
  • process.nextTick(Node.js)
  • Object.observe
  • MutationObserver

宏任务和微任务到底是怎样的执行顺序

事件循环的整体执行规则是

  • 所有代码作为宏任务进入主线程执行栈,开始执行
  • 执行过程中,同步代码会立即执行,宏任务进入宏任务队列,微任务进入微任务队列
  • 在执行当前宏任务之前,先读取微任务队列,有则执行,没有执行宏任务
  • 本轮宏任务执行完成,回到第2步,继续执行,直至宏任务与微任务队列全部清空
  1. 第一个宏任务由浏览器创建,即 script 标签所包裹/引用的同步代码,可以将 script 标签看做是其它语言中的 main 函数,所以一开始必然是 main 这个宏任务「先执行」。
  2. 在「当前」宏任务的执行末尾,会检查微任务队列并清空,才会开启下一轮事件循环,也就是执行「下一个」宏任务。
  3. 宏任务与宏任务之间不相交,而宏任务内涵了微任务,所以宏任务和微任务是包含关系。因此不能说当前宏任务包含了其它宏任务比如 setTimeout,只能说当前宏任务包含了「创建」下一个宏任务的代码。

综上,整个问题的通用说法根本就不是谁先执行的问题,而是一个宏任务要想结束,必然要去检查且清空微任务队列,才能开启下一轮事件循环(执行下一个宏任务),换句话说,一个宏任务即代表一个事件循环。

所以从完整的js代码来说

  • 先执行了script标签这个最大的宏任务,然后执行同步代码,执行了微任务。在微任务结束后,算是一次事件循环结束。
  • 执行下一个宏任务,清空微任务队列。
  • 这样执行下去,才有“宏任务先执行,微任务后执行”的说法

那么面试馆的角度,他的问题根本上就是想要问:setTimeout和Promise.then谁先执行

那么从这个问题上来说,是Promise.then先执行。因为setTimeout是宏任务,Promise.then是微任务,在执行宏任务之前先清空微任务。

写在最后

这是我的理解,如果有什么不对欢迎各位大佬指正

参考链接:

什么是事件循环 Event Loop - 掘金 (juejin.cn)

深入理解JS执行机制 - 掘金 (juejin.cn)

我入职了 - 掘金 (juejin.cn)以及评论区

作者:前端实习生鲸落
链接:juejin.cn/post/725928…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。