首先介绍几个相关的概念
-
执行栈
我们知道
执行栈
会存储和管理代码执行过程中的执行上下文,通过频繁的入栈退栈操作执行上下文,并执行每个执行上下文内部的代码。常规的同步代码就是这样执行的。那么异步代码怎么执行呢?首先我们明确一个概念:
任务源
。什么是任务源?比如我们在同步代码中开启一个定时器setTimeOut,这个setTimeOut就是任务源
,setTimeOut内部会传入回调函数,这个回调函数即为要执行的异步代码
。执行栈在遇到
任务源
时,不会像执行同步代码那样直接执行它的回调函数,而是会将异步代码添加任务队列
中。 -
任务队列
上面我们提到了
任务队列
是用来存放异步代码的容器。任务队列根据其存放异步代码的类型可以分为两种,宏任务队列
和微任务队列
,分别用来存放宏任务
和微任务
。 -
宏任务&微任务
一段异步代码属于
宏任务
还是微任务
其实是由任务源决定的。常见的宏任务:setTimeout、setInterval、requestAnimationFrame内部的回调、script(整体代码)
常见的微任务:promise.then中的回调,nextTick中传入的回调,MutationObserver
事件循环机制流程
- 主线程生成执行栈开始执行一段代码,同步代码优先执行,执行过程中遇到任务源时,判断这个任务源是宏任务源还是微任务源。
- 如果是
宏任务源
,将其内部宏任务
添加到宏任务队列
中,如果是微任务源
,将其内部微任务
添加到微任务队列
中 - 等同步代码执行完毕,执行栈进入闲置状态,检查
微任务队列
中是否有微任务
,如果有,执行栈会依次执行这些微任务。 - 渲染UI
- 检查
宏任务队列
中是否有宏任务
,如果有,取出宏任务队列中最前面的那个宏任务,推入到执行栈中开始执行。执行完毕后重复步骤3-5直到宏任务队列和微任务队列全部清空
注意:
根据上面的流程,我们知道如果一段同步代码中同时有一个宏任务源和一个微任务源,那么微任务一定会比宏任务先执行
每次执行栈在访问微任务队列时,会将当前微任务队列清空,但访问宏任务队列时,只会执行队列最上方的宏任务