事件循环练习
练习 1
分析以下代码的执行顺序:
Promise.resolve().then(() => {
console.log('p1');
setTimeout(() => {
console.log('s2');
}, 0);
});
setTimeout(() => {
console.log('s1');
Promise.resolve().then(() => {
console.log('p2');
});
});
执行步骤如下:
- 初始化宏任务队列:
s1,s2 - 初始化微任务队列:
p1,p2 - 执行同步代码,将
Promise.resolve().then的回调加入微任务队列,setTimeout的回调加入宏任务队列 - 同步代码执行完毕,执行微任务队列
- 输出
p1,将setTimeout的回调加入宏任务队列 - 微任务队列执行完毕,执行宏任务队列
- 输出
s1,将Promise.resolve().then的回调加入微任务队列 - 执行微任务队列,输出
p2 - 微任务队列执行完毕,执行下一个宏任务
- 输出
s2
因此最终输出顺序为: p1, s1, p2, s2
执行顺序遵循: 同步代码 -> 微任务代码 -> 渲染 -> 宏任务代码
练习 2
分析以下代码的执行顺序:
console.log(1);
setTimeout(() => {
console.log(2);
}, 10);
new Promise(function (resolve, reject) {
console.log(3);
resolve('');
console.log(4);
}).then((res) => {
console.log(5);
});
console.log(6);
执行步骤:
- 输出
1 - 将
setTimeout的回调加入宏任务队列 - 执行
new Promise的 executor,输出3 - 遇到
resolve,将then的回调加入微任务队列 - 继续执行 executor,输出
4 - executor 执行完毕,输出
6 - 同步代码执行完毕,执行微任务队列
- 执行
then的回调,输出5 - 微任务队列执行完毕,执行下一个宏任务
- 输出
2
因此最终输出顺序为: 1, 3, 4, 6, 5, 2
练习 3
分析以下代码的执行顺序:
const oBtn = document.getElementById('btn');
oBtn.addEventListener(
'click',
() => {
console.log(1);
Promise.resolve('m1').then((str) => {
console.log(str);
});
},
false
);
oBtn.addEventListener(
'click',
() => {
console.log(2);
Promise.resolve('m2').then((str) => {
console.log(str);
});
},
false
);
oBtn.click();
执行步骤:
- 为
btn元素绑定两个点击事件回调 - 调用
oBtn.click()模拟点击事件 - 执行第一个点击回调,输出
1 - 将
Promise.resolve('m1').then的回调加入微任务队列 - 执行第二个点击回调,输出
2 - 将
Promise.resolve('m2').then的回调加入微任务队列 - 点击回调执行完毕(属于宏任务),执行微任务队列
- 输出
m1,m2
因此最终输出顺序为: 1, 2, m1, m2
addEventListener绑定的事件回调属于宏任务
练习 4
分析以下代码的执行顺序:
setTimeout(() => {
console.log('setTimeout1');
setTimeout(() => {
console.log('setTimeout3');
}, 1000);
Promise.resolve().then((data) => {
console.log('then3');
});
});
Promise.resolve().then((data) => {
console.log('then1');
console.log('then4');
Promise.resolve().then((data) => {
console.log('then6');
});
});
Promise.resolve().then((data) => {
console.log('then2');
console.log('then5');
setTimeout(() => {
console.log('setTimeout2');
}, 1000);
});
console.log(2);
执行步骤:
- 将第一个
setTimeout的回调加入宏任务队列 - 将第一个
Promise.resolve().then的回调加入微任务队列 - 将第二个
Promise.resolve().then的回调加入微任务队列 - 输出
2 - 执行微任务队列,依次输出
then1,then4,then2,then5,并将then6的回调加入微任务队列 - 继续执行微任务队列,输出
then6 - 微任务队列执行完毕,执行下一个宏任务,即第一个
setTimeout - 输出
setTimeout1,将内部的setTimeout加入宏任务队列,将then3加入微任务队列 - 执行微任务队列,输出
then3 - 执行下一个宏任务,即第二个
setTimeout,输出setTimeout2 - 执行最后一个宏任务,即最内层的
setTimeout,输出setTimeout3
因此最终输出顺序为:2, then1, then4, then2, then5, then6, setTimeout1, then3, setTimeout2, setTimeout3
先调用的then一定先于后调用的then输出
练习 5
分析以下代码的执行顺序:
async function async1() {
console.log('a1.start');
await async2();
console.log('a1 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 - 将
setTimeout的回调加入宏任务队列 - 调用
async1(),输出a1.start - 遇到
await,调用async2(),输出async2,await后面的代码相当于then的回调,加入微任务队列 - 执行
new Promise的 executor,输出promise1 - 遇到
resolve,将then的回调加入微任务队列 - 输出
script end - 同步代码执行完毕,执行微任务队列
- 输出
a1 end,promise2 - 微任务队列执行完毕,执行下一个宏任务
- 输出
setTimeout
因此最终输出顺序为: script start、 a1.start 、 async2、 promise1、 script end、 a1 end、promise2、setTimeout
await后面的代码相当于Promise.then的回调,会被加入微任务队列

通过这些练习题,我们可以总结出事件循环的一些规律:
- 同步代码优先执行,将异步任务加入对应的宏任务队列或微任务队列
- 当同步代码全部执行完后,检查并清空微任务队列
- 执行 UI 渲染工作
- 取出宏任务队列的首个任务执行,检查并清空微任务队列,依此循环
- 先绑定的事件回调先执行
await后面的代码相当于Promise.then的回调