Open jtwang7 opened 3 years ago
一道事件循环面试考题:
async1 = async () => {
console.log('async1 start');
await async2();
console.log('async1 end');
}
function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
})
async1();
new Promise((resolve) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
return new Promise(function (resolve) {
resolve();
})
}).then((res) => {
console.log('promise3');
})
console.log('script end');
/**
* script start
* async1 start
* async2
* promise1
* script end
* async1 end
* promise2
* promise3
* setTimeout
*/
async1 = async () => {
// 4. 打印 'async1 start'
console.log('async1 start');
// 5. 执行 async2,遇到 await,将后续代码放入微任务队列等待执行,交还执行权(跳出该函数体)
await async2();
// 5.1 被放入微任务队列
// 10. 打印 async1 end
console.log('async1 end');
}
function async2() {
console.log('async2');
}
// 1. 打印 'script start'
console.log('script start');
// 2. 执行计时器,并向宏任务队列注册回调
setTimeout(() => {
console.log('setTimeout');
})
// 3. 执行 async1
async1();
// 6. 创建 promise 实例并执行 executor
new Promise((resolve) => {
// 7. 打印 promise1
console.log('promise1');
// 8. 执行 resolve() 并将 then 回调注册至微任务队列
resolve();
}).then(() => {
// 11. 打印 promise2
console.log('promise2');
return new Promise(function (resolve) {
// 12. 注册 then 回调至微任务队列
resolve();
})
}).then((res) => {
// 13. 打印 promise3
console.log('promise3');
})
// 9. 打印 script end
console.log('script end');
// 主线程清空,接着清空微任务队列,将微任务队列提到主线程执行👆
参考文章:
事件循环机制
概念
Event Loop 实际就是 JavaScript 异步执行机制的一种实现方式; 程序按照主线程-微任务-宏任务的顺序不断重复执行, 并始终维护各执行队列直至全部队列清空的操作就是 Event Loop;
流程
JavaScript 是单线程语言, JS 任务需要遵循一定的顺序执行。为了避免某个任务执行时间过长而阻塞后面任务的执行, JS 将任务分为了同步和异步任务,而同步任务和异步任务执行的场所不同,因此执行的过程也有所差异:
异步任务又被细分为宏任务和微任务, JS 在处理宏任务和微任务时又遵循特殊的执行顺序: 当 JS 遇到宏任务时, 将其放入 Macro Event Queue 中, 而微任务会被放入 Micro Event Queue 中(注意宏任务队列和微任务队列不是一个队列); 在读取(向外拿)回调函数时, 先清空微任务队列中的回调函数, 然后再从宏任务队列中调用并执行一个回调函数; (换句话说, 每一次宏任务执行前, 要清空上一次的微任务队列, 宏任务在微任务之后执行); 宏任务中可能产生新的微任务,而这些微任务的回调会被注册到微任务队列中,因此我们取出一个宏任务并执行完毕后,要再一次确认微任务队列是否被清空,若没有则要清空微任务队列,然后再从宏任务队列中调取下一个回调函数......
概括而言,JS 的执行顺序, 应遵循的思路为:
同步放入主线程 -> 执行同步代码(清空主线程代码) -> 遇到异步代码, 放入 Event Table -> Event Table 中判断宏任务 or 微任务 -> 注册回调放入各自队列 -> 若主线程为空,清空微任务(将微任务提到主线程执行) -> (若执行后又推入了新的微任务,又回到了主线程为空,微任务队列存在回调的情况,重复上述流程) -> ...... (若主线程/微任务队列都为空) -> 执行下一次宏任务(取出宏任务队列中的一个回调到主线程,然后执行。) -> … -> (若执行后推入了新的微任务,又回到了主线程为空,微任务队列存在回调的情况,重复上述流程)
宏任务 & 微任务
在通过例子深入了解 JS 执行机制前, 我们需要记住几个常用的宏任务和微任务: 宏任务:
微任务:
实例讲解
例一
例二
例三
经过上述三个例子, 你可能对 JS 执行机制有了大致的了解:
考核
回调函数内可能嵌套了多层, 但遵循上述步骤仍可以正确判断, 我们每次只需关注最外层嵌套函数即可, 在原有上下文基础上构建第二级的执行上下文, 清空主线程, 清空微队列, 再获取下一个宏任务回调执行并判断, 遇到嵌套后再循环…