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

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

javascript 复制代码
宏任务先执行还是微任务?
setTimeout比Promise.then()先执行?

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

javascript 复制代码
宏任务先执行。
setTimeout比Promise.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)

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

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

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

dart 复制代码
宏任务 >= 同步任务 => 微任务 | 
宏任务 >= 同步任务 => 微任务 | 
宏任务 >= 同步任务 => 微任务 |
......

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

html 复制代码
<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')
    }, 0)

    Promise.resolve().then(() => {
        console.log('a3')
    })
    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

js 复制代码
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

javascript 复制代码
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的解释里是把任务分类,同类型的任务放在相同的队列里,浏览器根据队列的优先级进行获取执行,例如有定时队列、微队列等等,微队列的优先级最高。但新规范对宏任务微任务这类题的输出顺序并无影响,大家遇到这种题还是可以照常回答。

相关推荐
小镇程序员28 分钟前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
疯狂的沙粒39 分钟前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪1 小时前
AJAX的基本使用
前端·javascript·ajax
力透键背1 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript
程楠楠&M1 小时前
node.js第三方Express 框架
前端·javascript·node.js·express
weiabc1 小时前
学习electron
javascript·学习·electron
想自律的露西西★1 小时前
用el-scrollbar实现滚动条,拖动滚动条可以滚动,但是通过鼠标滑轮却无效
前端·javascript·css·vue.js·elementui·前端框架·html5
白墨阳2 小时前
vue3:瀑布流
前端·javascript·vue.js
霍先生的虚拟宇宙网络2 小时前
webp 网页如何录屏?
开发语言·前端·javascript
温吞-ing2 小时前
第十章JavaScript的应用
开发语言·javascript·ecmascript