开始本文之前,让我们先看看下面这两个问题:
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的解释里是把任务分类,同类型的任务放在相同的队列里,浏览器根据队列的优先级进行获取执行,例如有定时队列、微队列等等,微队列的优先级最高。但新规范对宏任务微任务这类题的输出顺序并无影响,大家遇到这种题还是可以照常回答。