likes
comments
collection
share

是宏任务先执行还是微任务?

作者站长头像
站长
· 阅读数 19

开始本文之前,让我们先看看下面这两个问题:

宏任务先执行还是微任务?
setTimeoutPromise.then()先执行?

先说出我心中认为的答案:

宏任务先执行。
setTimeoutPromise.then()后执行。

上面的答案似乎是有矛盾的,既然认为宏任务先执行,而setTimeout是宏任务,Promise.then()是微任务,那应该是setTimeout先执行,为何又说setTimeout后执行呢?让我们接着往下看。

什么是宏任务、微任务?

我们都知道js是单线程的,js引擎在执行时的原则非常的简单:获取任务 >= 执行任务,反复重复此过程,直到没有任务可执行为止。

而任务分为同步任务和异步任务,异步任务又分为宏任务和微任务。

常见的宏任务有:

  • script标签
  • setTimeout、setInterval
  • setImmediate(Node)
  • requestAnimationFrame(浏览器)
  • I/O操作,如文件读取。
  • DOM事件

常见的微任务有:

  • Promise的resolve和reject
  • await
  • Object.observe
  • MutaionObserver
  • process.nextTick(Node)

区分了宏任务和微任务,接下来我们看看执行过程中的顺序又是怎样的?

首先执行宏任务,在执行这个宏任务过程中,遇到同步代码则立即推入执行栈执行,遇到微任务则将其追加入到微任务队列中,遇到宏任务则将其追加到宏任务队列中,等待全部同步代码执行完成后,将微任务队里的微任务逐个推入执行栈中执行,直到微任务队列为空,到此为一个循环。

下一个循环从宏任务队列中取出一个宏任务重复上面的过程,直至队列清空为止。

宏任务 >= 同步任务 => 微任务 | 
宏任务 >= 同步任务 => 微任务 | 
宏任务 >= 同步任务 => 微任务 |
......

下面通过代码示例来加深一下该流程:

<script>
    console.log('1')

    setTimeout(() => {
        console.log('2')
        Promise.resolve().then(() => {
            console.log('3')
        })
    }, 0)

    Promise.resolve().then(() => {
        console.log('4')
    })

    console.log('5')
</script>
<script>
    console.log('a1')

    setTimeout(() => {
        console.log('a2')
        Promise.resolve().then(() => {
            console.log('a3')
        })
    }, 0)

    Promise.resolve().then(() => {
        console.log('a4')
    })

    console.log('a5')
</script>

上面代码中,我们有两个script代码块,相当于两个宏任务,第一个宏任务被首先执行。

是宏任务先执行还是微任务?

同步任务被执行,此时输出1,5,而宏任务和微任务则被添加到对应的队列中,接着检查并执行微任务队列里的任务,这里只有一个,此时输出4,至此一轮循环结束。

接着检查宏任务队列,取第一个宏任务开始下一轮循环,遇同步任务输出2,同步任务执行完毕,还剩一个微任务随即被执行,输出3,至此第一个script执行完,最后执行第二个script,过程与第一个相同。

所有上面完整的输出结果为:1 5 4 2 3 a1 a5 a4 a2 a3

new Promise

new Promise(resolve => {
    console.log(1)
    resolve()
    console.log(2)
}).then(() => {
    console.log('3')
})
console.log('4') 

new Promise()里是一个构造函数,是一个同步任务,后面的then是一个微任务,所以输出结果:1 2 4 3

await

async function func() {
    console.log('a1')
    await  Promise.resolve()
    console.log('a2')
}

console.log(1)
func()
Promise.resolve().then(() => {
    console.log(2)
})
console.log(3)

await是基于Generator实现的,效果等同于Promise.then,所以await后面的代码可以相当于微任务。

输出结果为:1 a1 3 a2 2

到底哪个先执行?

回到我们开头的问题宏任务先执行还是微任务先执行?,其实这个问法是不够严谨的,因为script是宏任务,说宏任务先执行是没毛病的,毕竟代码都被包裹在script里。而如果站在执行过程中的视角来看,则当然是先执行微任务,微任务全部执行完了才会去执行剩下的宏任务,因此这种问法并没有明确的前提,容易产生歧义,所以也不必太纠结,理解其中的过程就好了。

当然如果你遇到面试官实在这么问,那就把问题反抛给他,给他来个三连问。

script是不是宏任务?
那是不是宏任务先执行?
执行宏任务过程中遇到微任务和宏任务难道不是先执行微任务?

补充

实际上最新的规范里已经删掉宏任务的概念了,W3C的解释里是把任务分类,同类型的任务放在相同的队列里,浏览器根据队列的优先级进行获取执行,例如有定时队列、微队列等等,微队列的优先级最高。但新规范对宏任务微任务这类题的输出顺序并无影响,大家遇到这种题还是可以照常回答。