前端周记
首发于前端周记

Javascript 基础夯实——理解 Event Loop、Micro Task & Macro Task

首先就标题上的三个名词做个名词解释:)

Event Loop:事件循环
Micro Task:微任务
Macro Task:宏任务

所以这次的主体呼之欲出:理解 Javascript 中的事件循环、微任务与宏任务机制

在开始本文之前,小伙伴们可以先去看之前的一篇文章,在这篇文章中略微提及了本篇所述的内容

前端面试题——深入探究 setTimeout


Tasks & Stack

Javascript 中的事件循环是以任务为单位的,将很多个待执行的任务串联在一起就形成了队列Task Queue,很多的队列先后按顺序执行任务就形成了Event Loop,所以要理解 Event Loop,就需要先了解 task 的执行过程,请看下图




Micro Task

我们知道调用alert时会阻塞页面的渲染,其实这是因为渲染页面总是在任务队列全部执行结束后才会开始,用示意图表示如下




那么 Micro Task 又是什么执行呢?Micro Task 也是在任务队列执行结束后才执行(或者栈为空时),但是却会先于渲染页面,它就像一条挂在任务队列后面的小尾巴匆匆执行完自己的任务,但是如果这条小尾巴太过繁重,也会影响到页面的渲染




Micro Task 会维护一条自己的队列,同样也存在入栈和出栈的操作

那么有哪些操作是 Micro Task 呢?Promise 就是其中之一

Macro Task

Marco Task 会在当前所有的任务全部执行结束后执行,也就是说,如果当前存在渲染页面,Marco Task 会在页面渲染结束之后执行,所以 Macro Task 不会导致页面的阻塞




我们常用的setTimeout 就是 Macro Task,同时setImmediate 等也是,这也是我们总是将alert写在setTimeout中的原因

举例论证:)


function func1 () {
    console.log('func 1')
}
function func2 () {
    console.log('func 2')
}
function func3 () {
    console.log('func 3')
}
function func4 () {
    console.log('func 4')
}

const func = function () {
    func1()
    setTimeout(func2, 0)
    Promise.resolve().then(
        () => func3()
    )
    func4()
}()

上面一串代码执行后,在控制台会按如下顺序打印出以下内容:

func1
func4
func3
func2

其实根据上文的内容很容易就能够推断出打印内容的顺序,但是我们依然对其进行从入栈和出栈的角度分析一下

// 任务队列
// 开始时无任务
task queue: []

// micro task queue
micro queue: []

// macro task queue
macro queue: []

// 栈
stack: []

执行立即执行函数func,func入栈:

task queue: [func]
micro queue: []
macro queue: []
stack: [func]

执行func1时,func1入栈:

task queue: [func, func1]
micro queue: []
macro queue: []
stack: [func, func1]

func1执行结束,出栈:

task queue: [func]
micro queue: []
macro queue: []
stack: [func]

setTimeout进入 marco task,不执行其回调:

task queue: [func]
micro queue: []
macro queue: [setTimeout]
stack: [func]

promise进入 micro task,不执行其回调

task queue: [func]
micro queue: [promise]
macro queue: [setTimeout]
stack: [func]

执行func4并入栈:

task queue: [func, func4]
micro queue: [promise]
macro queue: [setTimeout]
stack: [func, func4]

func4执行结束并出栈:

task queue: [func]
micro queue: [promise]
macro queue: [setTimeout]
stack: [func]

func执行结束并出栈:

task queue: []
micro queue: [promise]
macro queue: [setTimeout]
stack: []

此时 stack 已空,任务全部执行结束,开始执行 micro task promiseThen:

task queue: []
micro queue: [promise]
macro queue: [setTimeout]
stack: [promiseThen]

promiseThen执行结束出栈:

task queue: []
micro queue: []
macro queue: [setTimeout]
stack: []

如果需要重新渲染页面的话,则会在此时执行,如果不需要,则开始执行 macro tasksetTimeoutCallback

task queue: []
micro queue: []
macro queue: [setTimeout]
stack: [setTimeoutCallback]

setTimeoutCallback执行结束出栈:

task queue: []
micro queue: []
macro queue: []
stack: []

以上就是这个简单的例子的任务执行顺序

Event Loop

看到这里,你应该已经知道 Event Loop 是什么了,上面的例子是 Event Loop 中的一条,而执行完这一整条之后,还会有下一条,下下条,它们在一起就组成了完整的 Event Loop 机制,周而复始不断循环

当然,关于 Event Loop 的解释并不只有这一种,但是本质上都是相同的,micro task 和 macro task 会区别于 主任务 / 主线程 的队列,并且在程序的整个生命周期中不停的重复 主任务 ——> micro task ——> 渲染视图 ——> macro task 的操作

最后再厚颜无耻的求下关注:)扫描题图二维码,关注前端周记微信公众号,感谢小伙伴们的支持哦!

编辑于 2017-11-20

文章被以下专栏收录