在浏览器的事件循环中,首先大家要认清楚 3 个角色:函数调用栈、宏任务(macro-task)队列和微任务(micro-task)队列。
常见的 macro-task 比如: setTimeout、setInterval、 setImmediate、 script(整体代码)、I/O 操作等。
常见的 micro-task 比如: process.nextTick、Promise、MutationObserver 等
注意:script(整体代码)它也是一个宏任务;此外,宏任务中的 setImmediate、微任务中的 process.nextTick 这些都是 Node 独有的。
一个完整的 Event Loop 过程,可以概括为以下阶段:
1.执行并出队一个 macro-task。注意如果是初始状态:调用栈空。micro 队列空,macro 队列里有且只有一个 script 脚本(整体代码)。这时首先执行并出队的就是 script 脚本;
2.全局上下文(script 标签)被推入调用栈,同步代码执行。在执行的过程中,通过对一些接口的调用,可以产生新的 macro-task 与 micro-task,它们会分别被推入各自的任务队列里。这个过程本质上是队列的 macro-task 的执行和出队的过程;
3.上一步我们出队的是一个 macro-task,这一步我们处理的是 micro-task。但需要注意的是:当 macro-task 出队时,任务是一个一个执行的;而 micro-task 出队时,任务是一队一队执行的(如下图所示)。因此,我们处理 micro 队列这一步,会逐个执行队列中的任务并把它出队,直到队列被清空;
4.执行渲染操作,更新界面;
5.检查是否存在 Web worker 任务,如果有,则对其进行处理。
UI渲染
根据HTML Standard,一轮事件循环执行结束之后,下轮事件循环执行之前开始进行 UI render。即:macro-task任务执行完毕,接着执行完所有的micro-task任务后,此时本轮循环结束,开始执行UI render。UI render完毕之后接着下一轮循环。