近几天看了好多js基础原理相关的知识。同步代码&异步代码的执行顺序(浏览器的事件循环&宏任务&微任务)一直是面试的高频考题,这里总结一下自己做这种题的思路(流程)。
JS代码中的异步任务可进一步分为宏任务(macrotask)与微任务(microtask)。
宏任务包括:script代码、setTimeout、setInterval、I/O、UI render
微任务包括:promise.then、Object.observe(已废弃)、MutationObserver
-
看了很多文章,网上对于宏微任务的执行顺序有一点点不一致,我更倾向于下面的版本。
遇到异步任务的时候,先判断是哪种任务,然后追加到相应的任务队列中。当执行同步代码时,遇到宏微任务时就将其添加到对应的队列中;当同步代码执行完毕,浏览器会先去清空微任务队列:依次取出微任务,并执行,执行过程中如果出现新的微任务,就继续添加到当前
微任务队列的末尾,如果遇到同步任务,就添加到同步任务的队列末尾;当微任务清空之后,浏览器会从宏任务中取出队首任务并执行,在执行过程中,遇到微任务就追加到微任务的队列末尾,如果遇到宏任务,就追加到宏任务的队列末尾,当此宏任务执行完毕,马上去清空当前微任务的队列,如此反复,直至宏任务队列 & 微任务队列全部清空。
EventLoop循环 (图片来自https://juejin.im/post/5e01e0f76fb9a01622779fad)
-
了解执行原理后,就做一些题,锻炼一下:
-
题目1 - 较简单
setTimeout(function(){ //s1
console.log('1')
});
new Promise(function(resolve){ // p1-resolve
console.log('2');
resolve();
}).then(function(){ // p1-then
console.log('3')
});
console.log('4');
// 输出 2 4 3 1
步骤:
1. 主代码执行
宏任务队列 = [s1]
进入Promise声明,console.log('2'); ======================> 2
微任务队列 = [p1-then]
console.log('4'); ======================> 4
2. 清掉微任务队列
console.log('3') ======================> 3
微任务队列 = []
3. 拿出宏任务队列第一项 s1
宏任务队列 = []
console.log('1') ======================> 1
该宏任务结束
- 题目2 - 稍复杂一些
console.log(1);
setTimeout(() => { // s1
console.log(2);
Promise.resolve().then(() => { // p1-then
console.log(3);
});
}, 0);
new Promise((resolve, reject) => { // p2-resolve
console.log(4);
resolve(5);
}).then((data) => { // p2-then
console.log(data);
});
setTimeout(() => { // s2
console.log(6);
}, 0);
// 输出 1 4 5 2 3 6
步骤:
1. 主代码执行
console.log(1); ======================> 1
宏任务队列 = [s1]
进入Promise声明 ======================> 4
微任务队列 = [p2-then]
宏任务队列 = [s1,s2]
2. 清掉微任务队列
console.log(data); ======================> 5
微任务队列 = []
3. 拿出宏任务队列中第一项 s1
宏任务队列 = [s2]
console.log(2); ======================> 2
微任务队列 = [p1-then]
该宏任务结束
4. 清空微任务队列
console.log(3); ======================> 3
微任务队列 = []
5. 拿出宏任务队列中第一项 s2
宏任务队列 = []
console.log(6); ======================> 6
该宏任务结束
- 题目3 - 有await,await后续作为Promise.then即可
async function async1() {
console.log(1);
const result = await async2();
console.log(3); // p-await
}
async function async2() {
console.log(2);
}
Promise.resolve().then(() => { // p1
console.log(4);
});
setTimeout(() => { // s1
console.log(5);
});
async1();
console.log(6);
// 输出 1 2 6 4 3 5
步骤:
1. 主代码执行
微任务队列 = [p1]
宏任务队列 = [s1]
console.log(1) ======================> 1
console.log(2) ======================> 2
await方法返回的是一个promise对象,因此await方法执行完毕的后续代码都应该归入微任务队列,
所以console.log(3)应该作为微任务,追加到微任务队列中。
微任务队列 = [p1, p-await]
console.log(6); ======================> 6
2. 清空微任务队列
console.log(4); ======================> 4
console.log(3); ======================> 3
微任务队列 = []
3. 拿出宏任务队列第一项 s1
宏任务队列 = []
console.log(5); ======================> 5
- 题目4 - 较复杂
setTimeout(() => { // s1
console.log('setTimeout - 1')
setTimeout(() => { // s1-1
console.log('setTimeout - 1 - 1')
})
new Promise(resolve => { // p1-resolve
console.log('setTimeout - 1 - resolve')
resolve()
}).then(() => { // p1-then
console.log('setTimeout - 1 - then')
new Promise(resolve => resolve()).then(() => { // p1-then-then
console.log('setTimeout - 1 - then - then')
})
})
})
setTimeout(() => { // s2
console.log('setTimeout - 2')
setTimeout(() => { // s2-1
console.log('setTimeout - 2 - 1')
})
new Promise(resolve => resolve()).then(() => { // p2-then
console.log('setTimeout - 2 - then')
new Promise(resolve => resolve()).then(() => { // p2-then-then
console.log('setTimeout - 2 - then - then')
})
})
})
// 输出
// setTimeout - 1
// setTimeout - 1 - resolve
// setTimeout - 1 - then
// setTimeout - 1 - then - then
// setTimeout - 2
// setTimeout - 2 - then
// setTimeout - 2 - then - then
// setTimeout - 1 - 1
// setTimeout - 2 - 1
步骤:
1. 主代码执行
宏任务队列 = [s1 , s2]
2. 拿出第一个宏任务 s1
宏任务队列 = [s2]
console.log('setTimeout - 1') ======================> setTimeout - 1
宏任务队列 = [s2 , s1-1]
console.log('setTimeout - 1 - resolve') ======================> setTimeout - 1 - resolve
微任务队列 = [p1-then]
s1宏任务结束
3. 清空微任务队列
微任务队列 = []
console.log('setTimeout - 1 - then') ======================> setTimeout - 1 - then
微任务队列 = [p1-then-then]
4. 清空微任务队列
微任务队列 = []
console.log('setTimeout - 1 - then - then') ======================> setTimeout - 1 - then - then
5. 拿出第一个宏任务 s2
宏任务队列 = [s1-1]
console.log('setTimeout - 2') ======================> setTimeout - 2
宏任务队列 = [s1-1 , s2-1]
微任务队列 = [p2-then]
6. 清空微任务队列
微任务队列 = []
console.log('setTimeout - 2 - then') ======================> setTimeout - 2 - then
微任务队列 = [p2-then-then]
7. 清空微任务队列
微任务队列 = []
console.log('setTimeout - 2 - then - then') ======================> setTimeout - 2 - then - then
8. 拿出宏任务队列第一项 s1-1
宏任务队列 = [s2-1]
console.log('setTimeout - 1 - 1') ======================> setTimeout - 1 - 1
9. 拿出宏任务队列第一项 s2-1
宏任务队列 = []
console.log('setTimeout - 2 - 1') ======================> setTimeout - 2 - 1
网友评论