事件循环再认识
宏任务与微任务
前面我们已经简单给大家介绍了浏览器器中的事件循环,如图:
事件循环中的异步队列有两种:宏任务( macro )队列和微任务( micro )队列。
宏任务队列有一个,微任务队列只有一个。
- 常见的宏任务有:setTimeout、setInterval、requestAnimationFrame、script等。
- 常见的微任务有:new Promise( ).then(回调)、MutationObserver 等。
一个完整的事件循环过程,可以概括为以下阶段:
- 查看执行栈中是否有同步代码,先执行同步代码
- 如果执行栈中的同步代码清空,就会从浏览器宿主中调入对应的异步代码
- 宏任务加入到宏任务队列,微任务加入到微任务队列
- 如果事件队列中,同时存在宏任务与微任务,那么先将事件队列中的所有微任务加入到执行栈中执行,直到将执行栈清空
- 然后再将事件队列中的所有宏任务加入到执行栈中执行,直到将执行栈清空
- 如此循环,直到所有代码执行完成
宏任务与微任务谁先执行?
但是,如果你仔细研究这个图,其实你会发现一个问题,首先进入执行栈的,其实是宏任务 如下:
但是,这里第一出现的宏任务,其实是浏览器解析html和script脚本的任务,也就是第一次加载执行同步代码的时候,就是所谓最早的宏任务。
我这里的说法,为了大家不在这里绕晕,你可以完全忽略所谓的最早的宏任务,就把这个直接说成执行栈中加入同步代码,然后再区分微任务和宏任务,所以你可以这里简单理解,当第一次加执行栈中加载完之后,微任务就是事件队列的VIP,微任务队列中有微任务,那么首先执行微任务,再去执行宏队列中的任务
因此,下面的分析中,我都直接忽略了第一次执行宏任务script的过程,直接做为加载执行栈进行讲解
当然这么说比较的抽象,我们通过一些面试题,就能理清楚这些脉络
面试题大多数都是围绕着Promise
和计时器
展开的,所有,大家必须要清楚,什么时候是同步代码,什么时候是异步代码,什么是微任务,什么是宏任务,什么时候将代码加入到执行栈中
面试题分析
例1、下面的代码执行的结果是什么?
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
答案:
script start
script end
promise1
promise2
setTimeout
分析:
首先会执行同步的任务,输出 script start 以及 script end。接下来是处理异步任务,异步任务分为宏任务队列和微任务队列,在执行宏任务队列中的每个宏任务之前先把微任务清空一遍,由于 promise 是微任务,所以会先被执行,而 setTimeout 由于是一个宏任务,会在微任务队列被清空后再执行。
例2、下面的代码执行的结果是什么?
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0)
})
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2')
})
},0)
答案:
Promise1
setTimeout1
Promise2
setTimeout2
分析:
先查看执行栈,然后将宏任务
setTimeout
与微任务Promise.resolve().then
分别推入队列,先执行微任务,打印Promise1
,然后发现宏任务setTimeout
,再次把第二个宏任务推入到队列。然后执行宏队列中的第一个
setTimeout
,打印setTimeout1
,发现第二个微任务Promise.resolve().then
,推入微任务队列。这时微任务与宏任务队列中都有内容,因此,还是先执行微任务,打印
Promise2
,然后再执行宏任务,打印setTimeout2
例3:下面的代码执行的结果是什么?
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
})
promise.then(() => {
console.log(3);
})
console.log(4);
答案:
1
2
4
3
分析:
注意Promise函数中的代码是同步代码,只有执行resolve()之后,才是异步代码
因此先执行
console.log(1); console.log(2);
,然后是console.log(4);
最后才是then中的异步代码console.log(3)
;
面试题
1:下面的代码执行的结果是什么?
const promise = new Promise((resolve, reject) => {
console.log(1);
setTimeout(()=>{
console.log(2)
resolve();
console.log(3);
})
})
promise.then(() => {
console.log(4);
})
console.log(5);
答案:
1
5
2
3
4
2:下面的代码执行的结果是什么?
async function m(){
const n = await 1;
console.log(n);
}
m();
console.log(2);
答案:
2
1
3:下面的代码执行的结果是什么?
async function m(){
const n = await 1;
console.log(n);
}
(async ()=>{
await m();
console.log(2);
})();
console.log(3);
答案:
3
1
2
4:下面的代码执行的结果是什么?
async function m1(){
return 1;
}
async function m2(){
const n = await m1();
console.log(n)
return 2;
}
async function m3(){
const n = m2();
console.log(n);
return 3;
}
m3().then(n=>{
console.log(n);
});
m3();
console.log(4);
答案:
Promise { <pending> [[PromiseResult]]: 2}
Promise { <pending> [[PromiseResult]]: 2}
4
1
3
1
5:下面的代码执行的结果是什么?
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
答案:
1
6:下面的代码执行的结果是什么?
var a;
var b = new Promise((resolve, reject) => {
console.log('promise1');
setTimeout(()=>{
resolve();
}, 1000);
}).then(() => {
console.log('promise2');
}).then(() => {
console.log('promise3');
}).then(() => {
console.log('promise4');
});
a = new Promise(async (resolve, reject) => {
console.log(a);
await b;
console.log(a);
console.log('after1');
resolve(true);
console.log('after2');
});
console.log('end');
答案:
promise1
undefined
end
promise2
promise3
promise4
Promise { <pending> }
after1
after2
7:下面的代码执行的结果是什么?
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
答案:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
Comments