JavaScript事件循环
什么是事件循环?
js是但线程语言,意味着同一时间只能执行一个任务,为了处理异步事件(网络请求、定时器、动画),js引入了事件循环机制,让单线程也能" 并发"地执行任务。
执行栈
执行栈(Call Stack)
- 是同步代码执行场所,
- 是 JavaScript 引擎用来跟踪函数调用的机制
- 遵循着先进后出的原则
js
function foo() {
foo2()
console.log("执行1")
}
function foo2() {
console.log("执行2")
}
foo()
// 执行栈变化:
// 1. foo() 入栈
// 2. foo2() 入栈
// 3. 执行 console.log("执行2")
// 4. foo2() 出栈
// 5. 执行 console.log("执行1")
// 6. foo() 出栈任务队列
任务队列(Task Queue)
- 是事件循环中用于存放异步任务回调的队列结构
宏任务和微任务
宏任务(Macro Task)和微任务(Micro Task)
- 宏任务每次执行一个,执行间隔允许浏览器进行渲染等操作
- 微任务在每次宏任务执行完毕后会清空整个队列
| 任务类型 | 优先级 | 包含任务 |
|---|---|---|
| 宏任务 | 低 | script 整体代码、setTimeout/setInterval、I/O、DOM 事件、postMessage、setImmediate(Node) |
| 微任务 | 高 | Promise.then/catch/finally、async/await、queueMicrotask、MutationObserver(浏览器)、process.nextTick(Node) |
提示
循环机制:先一个宏任务,然后执行并清空当前宏任务下所有微队列(无内外限制),然后再执行下一个宏任务,直到没有宏任务再清空微任务队列。
小试牛刀1
js
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');解答:
js
console.log('1'); // 同步代码 - 立即执行
// 宏任务:加入宏任务队列(待同步代码、微任务执行完后执行)
setTimeout(() => console.log('2'), 0);
// 微任务:Promise.resolve()是同步操作,.then回调加入微任务队列
Promise.resolve().then(() => console.log('3'));
console.log('4'); // 同步代码 - 立即执行
// 执行流程拆解:
// 1. 执行同步代码 → 输出 1、4
// 2. 清空微任务队列 → 执行.then回调,输出 3
// 3. 执行下一个宏任务(setTimeout回调)→ 输出 2
// 最终输出:1 → 4 → 3 → 2小试牛刀2
js
async function fn() {
console.log("5")
await Promise.resolve();
console.log("6")
}
console.log("1")
fn()
console.log("8")解答:
js
async function fn() {
console.log("5") // 3. 同步执行 → 输出 5
await Promise.resolve(); // 关键:await 做了两件事
// ① 同步执行右侧的 Promise.resolve()(无输出,仅返回已决议Promise)
// ② 将 await 下方的代码(console.log('6'))包装成「微任务」(微1),加入微任务队列
console.log("6") // 6. 微任务执行 → 输出 6(你标注的“同步代码”需修正:这是微任务,不是同步)
}
console.log("1") // 1. 同步执行 → 输出 1
fn() // 2. 同步调用 fn 函数,进入 fn 执行上下文
console.log("8") // 5. 同步执行 → 输出 8
// 结果:1 → 5 → 8 → 6进阶练习1
js
console.log('1');
setTimeout(() => {
console.log('2');
Promise.resolve().then(() => console.log('3'));
}, 0);
Promise.resolve().then(() => {
console.log('4');
queueMicrotask(() => console.log('5'));
setTimeout(() => console.log('6'), 0);
});
async function asyncFn() {
await Promise.resolve();
console.log('7');
}
asyncFn();
console.log('8');解答:
js
// ===== 第1阶段:执行script初始宏任务(同步代码)=====
// ① 同步执行 → 输出 1
console.log('1');
// ② 同步定义宏任务1(setTimeout输出2)→ 加入「宏任务队列」:[宏1]
setTimeout(() => {
console.log('2'); // ⑫ 执行宏任务1 → 输出 2
// 宏1内新增微任务(输出3)→ 加入「微任务队列」:[微4]
Promise.resolve().then(() => console.log('3')); // ⑬ 清空微任务队列 → 输出 3
}, 0);
// ③ 同步定义微任务1(Promise.then输出4)→ 加入「微任务队列」:[微1]
Promise.resolve().then(() => {
console.log('4');// ⑥ 执行微任务1 → 输出 4
// 微1内新增微任务3(输出5)→ 加入「微任务队列」:[微2, 微3]
queueMicrotask(() => console.log('5')); // ⑨ 执行微任务3 → 输出 5
// 微1内新增宏任务2(setTimeout输出6)→ 加入「宏任务队列」:[宏1, 宏2]
setTimeout(() => console.log('6'), 0); // ⑭ 执行宏任务2 → 输出 6
});
// ④ 同步执行asyncFn(),await后定义微任务2(输出7)→ 加入「微任务队列」:[微1, 微2]
async function asyncFn() {
await Promise.resolve(); // await后代码进入微任务队列(微2)
console.log('7');// ⑧ 执行微任务2 → 输出 7
}
asyncFn();
// ⑤ 同步执行 → 输出 8
console.log('8');
// ===== 第2阶段:清空微任务队列(script宏任务执行完后)=====
// ⑥ 执行微任务1 → 输出 4
// ⑦ 微1执行中:新增微3(输出5)→ 微队列变为 [微2, 微3];新增宏2 → 宏队列变为 [宏1, 宏2]
// ⑧ 执行微任务2 → 输出 7
// ⑨ 执行微任务3 → 输出 5
// ⑩ 微任务队列清空(无剩余)
// ===== 第3阶段:执行下一个宏任务(宏1)=====
// ⑫ 执行宏任务1 → 输出 2
// ⑪ 宏1执行中:新增微4(输出3)→ 微队列变为 [微4]
// ⑬ 清空微任务队列(执行微4)→ 输出 3
// ===== 第4阶段:执行下一个宏任务(宏2)=====
// ⑭ 执行宏任务2 → 输出 6
// 拓展版输出顺序:
// 1 → 8 → 4 → 7 →
// 5 → 2 → 6 → 3进阶练习2
js
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1();
new Promise((resolve) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
});
console.log('script end');解答:
js
async function async1() {
console.log('async1 start');
await async2();
// await: 1. 同步执行 async2();2. 将下方代码包装成微任务
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1();
new Promise((resolve) => {
console.log('promise1');
resolve(); // 同步触发 resolve,将 .then 回调加入微任务
}).then(() => {
console.log('promise2');
});
console.log('script end');
// 结果:script start → async1 start → async2 → promise1 → script end → async1 end → promise2 → setTimeout总结
- 事件循环是 JS 处理异步的核心,单线程通过 “栈 + 队列” 实现异步;
- 微任务优先级高于宏任务,微任务执行完才会执行下一个宏任务;
- 浏览器和 Node.js 的事件循环规则有差异,重点关注微任务执行时机;
- async/await 的本质是 Promise 微任务,await 后的代码会进入微任务队列。