Skip to content

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/ODOM 事件、postMessagesetImmediate(Node)
微任务Promise.then/catch/finallyasync/await
queueMicrotaskMutationObserver(浏览器)、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 后的代码会进入微任务队列。

这是我的个人文档