EventLoop又叫事件循环,用来控制浏览器中事件的执行过程。
浏览器JS执行过程
如下图所示,浏览器存在执行栈(单线程执行JS代码)、任务队列及一些WEB API。
在浏览器中运行如下代码:
console.log(1);
setTimeout(function(){
console.log(2);
}, 5000);
console.log(3);
// 输出:
// 1
// 3
// 2
复制代码
执行过程如下(演示地址):
- 执行
console.log
,输出1 - 执行
setTimeout
,启动定时器 - 执行
console.log
,输出3 - 定时器到达时间,把回调放入任务队列
- 执行栈是空的,从任务队列取任务,放入执行栈
- 执行回调中的
console.log
,输出2
理解了这个执行过程,就能明白为什么下面代码(setTimeout时间设为0)的输出结果与上面相同:
console.log(1);
setTimeout(function(){
console.log(2);
}, 0);
console.log(3);
// 输出:
// 1
// 3
// 2
复制代码
执行过程:
- 执行
console.log
,输出1 - 执行
setTimeout
,启动定时器 - 定时器到达时间,把回调放入任务队列
- 执行栈非空,继续执行
console.log
,输出3 - 执行栈为空,从任务队列取任务,放入执行栈
- 执行回调中的
console.log
,输出2
宏任务/微任务
并不是所有异步任务的执行优先级都相同,微任务(microtask)比宏任务(macrotask)要优先执行。
在浏览器环境中,常见的宏任务有setTimeout
、MessageChannel
、postMessage
、setImmediate
;常见的微任务有MutationObsever
和Promise.then
。
从下面这段代码来看浏览器的执行过程:
setTimeout(() => {
console.log('timeout1');
Promise.resolve().then(() => {
console.log('promise1');
});
Promise.resolve().then(() => {
console.log('promise2');
});
}, 0);
setTimeout(() => {
console.log('timeout2');
Promise.resolve().then(() => {
console.log('promise3')
});
}, 0);
// 输出:
// timeout1
// promise1
// promise2
// timeout2
// promise3
复制代码
以下为详细执行过程:
- 开始
- 执行栈:setTimeout,setTimeout
- 微任务队列:
- 宏任务队列:
- 输出:
- 执行第一个
setTimeout
,启动定时器1
- 执行栈:setTimeout
- 微任务队列:
- 宏任务队列:
- 输出:
定时器1
时间到,将回调T1
放入宏任务队列- 执行栈:setTimeout
- 微任务队列:
- 宏任务队列:回调T1
- 输出:
- 执行
setTimeout
,启动定时器2
- 执行栈:
- 微任务队列:
- 宏任务队列:回调T1
- 输出:
定时器2
时间到,将回调T2
放入宏任务队列- 执行栈:
- 微任务队列:
- 宏任务队列:回调T1,回调T2
- 输出:
- 执行栈为空,微任务队列为空,从宏任务队列中取出
回调T1
,放入执行栈- 执行栈:console.log,Promise.then,Promise.then
- 微任务队列:
- 宏任务队列:回调T2
- 输出:
- 执行
console.log
,输出timeout1- 执行栈:Promise.then,Promise.then
- 微任务队列:
- 宏任务队列:回调T2
- 输出:timeout1
- 执行
Promise.then
,将回调P1
放入微任务队列- 执行栈:Promise.then
- 微任务队列:回调P1
- 宏任务队列:回调T2
- 输出:timeout1
- 执行下一个
Promise.then
,将回调P2
放入微任务队列- 执行栈:
- 微任务队列:回调P1,回调P2
- 宏任务队列:回调T2
- 输出:timeout1
- 执行栈为空,从微任务队列取出
回调P1
,放入执行栈- 执行栈:console.log
- 微任务队列:回调P2
- 宏任务队列:回调T2
- 输出:timeout1
- 执行
console.log
,输出promise1- 执行栈:
- 微任务队列:回调P2
- 宏任务队列:回调T2
- 输出:timeout1,promise1
- 执行栈为空,从微任务队列取出
回调P2
,放入执行栈- 执行栈:console.log
- 微任务队列:
- 宏任务队列:回调T2
- 输出:timeout1,promise1
- 执行
console.log
,输出promise2- 执行栈:
- 微任务队列:
- 宏任务队列:回调T2
- 输出:timeout1,promise1,promise2
- 执行栈为空,微任务队列为空,从宏任务队列取出
回调T2
,放入执行栈- 执行栈:console.log,Promise.then
- 微任务队列:
- 宏任务队列:
- 输出:timeout1,promise1,promise2
- 执行
console.log
,输出timeout2- 执行栈:Promise.then
- 微任务队列:
- 宏任务队列:
- 输出:timeout1,promise1,promise2,timeout2
- 执行
Promise.then
,将回调P3
放入微任务队列- 执行栈:
- 微任务队列:回调P3
- 宏任务队列:
- 输出:timeout1,promise1,promise2,timeout2
- 任务队列为空,从微任务队列取出
回调P3
,放入执行栈- 执行栈:console.log
- 微任务队列:
- 宏任务队列:
- 输出:timeout1,promise1,promise2,timeout2
- 执行
console.log
,输出promise3- 执行栈:
- 微任务队列:
- 宏任务队列:
- 输出:timeout1,promise1,promise2,timeout2,promise3
- 完成
- 执行栈:
- 微任务队列:
- 宏任务队列:
- 输出:timeout1,promise1,promise2,timeout2,promise3
参考资料: