什么是Event Loop
event loop(事件循环) 是 JavaScript 中实现异步编程的核心机制,也是 JS 能够在单线程模型下处理大量并发任务的基础。
一句话理解
Event Loop 是协调同步任务与异步任务执行顺序的一套机制。 它负责不断从任务队列中取出任务执行,确保代码按正确的顺序运行。
在
JavaScript中,任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。
宏任务(MacroTask)
什么是宏任务(Macro Task)?
宏任务(Macro Task)是事件循环(Event Loop)中的一种任务类型,通常用于调度异步回调。
它代表着整个主线程执行过程中的一个阶段,每次事件循环,宏任务队列中的一个任务会被取出执行,执行完后才会处理所有微任务(Micro Task) ,然后再进入下一轮宏任务。
常见的宏任务包括
script全部代码、setTimeout、setInterval、setImmediate、I/O、UI Rendering。-
理解“宏任务队列”
宏任务会被放入一个任务队列(Task Queue)中,事件循环会从这个队列中每次取出一个宏任务来执行,然后再去清空微任务队列。每次只能执行一个宏任务,因此你可以认为:
每一个宏任务 = 一轮事件循环
什么是微任务(Microtask)?
微任务是 JavaScript 中一种异步任务类型,优先级高于宏任务,用于在当前事件循环结束前执行的短小任务。
它是在一轮事件循环中,当前宏任务执行完之后、下一轮宏任务执行前,立即执行的任务。
常见的宏任务包括
Process.nextTick(Node独有)、promise.then()、Object.observe(废弃)、MutationObserver、queueMicrotask()
同步任务和异步任务
Javascript单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。
事件循环流程
一个宏任务开始 -> 执行所有同步任务 -> 执行所有微任务 -> 执行下一个宏任务...
如图解释
举个例子(process.nextTick PK Promise.then)
console.log('start');
// node 微任务
// process 进程对象
process.nextTick(() => {
console.log('Process Next Tick');
});
// 微任务
Promise.resolve().then(() => {
console.log('Promise Resolved');
})
// 宏任务
setTimeout(() => {
console.log('haha');
Promise.resolve().then(() => {
console.log('inner Promise');
})
}, 0);
console.log('end');
逐步执行流程分析:
✅ Step 1:执行同步代码
console.log('start'); // 输出 1
process.nextTick(...) // 注册 nextTick 回调
Promise.resolve().then(...) // 注册微任务
setTimeout(...) // 注册宏任务
console.log('end'); // 输出 2
此时输出:
start
end
✅ Step 2:执行 process.nextTick()
Process Next Tick
✅ Step 3:执行微任务队列(Promise)
Promise Resolved
✅ Step 4:进入下一轮事件循环 → 执行 setTimeout 宏任务
setTimeout(() => {
console.log('haha'); // 输出 4
Promise.resolve().then(() => {
console.log('inner Promise'); // 注册微任务,输出 5
})
}, 0);
输出:
haha
✅ Step 5:执行 setTimeout 内的微任务(Promise)
inner Promise
✅ 最终输出顺序:
start
end
Process Next Tick
Promise Resolved
haha
inner Promise
可视化执行顺序:
同步阶段:
-> log: start
-> process.nextTick → 注册
-> Promise.then → 注册
-> setTimeout → 注册
-> log: end
微任务阶段:
-> process.nextTick → 执行
-> Promise.then → 执行
宏任务阶段(下一轮):
-> setTimeout → 执行 log: haha
-> setTimeout 中的 Promise.then → 执行 log: inner Promise
在 Node.js 环境下:
process.nextTick()比Promise.then()更早执行- 每个宏任务执行完后,都会清空所有微任务队列
setTimeout属于宏任务,要等同步 + 所有微任务跑完后再执行
一个例子不够? 再来一个
console.log('同步Start');
const promise1 = Promise.resolve('Frist Promise');
const promise2 = Promise.resolve('Second Promise');
const promise3 = new Promise(resolve => {
console.log('promise3');
resolve('Third Promise');
});
promise1.then(value => console.log(value));
promise2.then(value => console.log(value));
promise3.then(value => console.log(value));
setTimeout(() => {
console.log('下一把再相见');
const promise4 = Promise.resolve('Forth Promise');
promise4.then(value => console.log(value));
}, 0);
setTimeout(() => {
console.log('下下一把再相见');
}, 0);
console.log('同步End');
第一轮执行过程详解
▶️ 【同步任务阶段】立即执行
这些代码 立即执行,加入主线程:
console.log('同步Start'); // 输出 ✅
console.log('promise3'); // Promise 执行器是同步的 ✅
console.log('同步End'); // 输出 ✅
此时已输出:
同步Start
promise3
同步End
🕒 微任务阶段(本轮事件循环)
在第一轮 Event Loop 中,执行所有已注册的微任务(即 .then()):
promise1.then(...) // 输出 Frist Promise
promise2.then(...) // 输出 Second Promise
promise3.then(...) // 输出 Third Promise
依注册顺序执行,输出:
Frist Promise
Second Promise
Third Promise
🔁 下一轮事件循环(宏任务阶段)
两个 setTimeout 回调进入宏任务队列,按注册顺序执行:
⏱ 第一个 setTimeout
console.log('下一把再相见'); // 输出
const promise4 = Promise.resolve(...);
promise4.then(...) // 注册微任务
然后立刻执行微任务:
console.log('Forth Promise');
⏱ 第二个 setTimeout
console.log('下下一把再相见');
✅ 最终输出顺序总结:
同步Start // 同步任务
promise3 // Promise 构造函数是同步执行的
同步End
Frist Promise // 微任务
Second Promise
Third Promise
下一把再相见 // 宏任务 1
Forth Promise // 宏任务 1 中的微任务
下下一把再相见 // 宏任务 2
📌 最终输出(逐行):
同步Start
promise3
同步End
Frist Promise
Second Promise
Third Promise
下一把再相见
Forth Promise
下下一把再相见
代码一开始执行的是同步任务,所以首先输出的是 同步Start。接着创建了三个 Promise,其中 promise3 是通过 new Promise() 构造函数生成的,在构造函数内部执行了 console.log('promise3'),因为这是同步代码,也会立即输出。然后继续执行同步任务,输出 同步End。
同步任务执行完之后,进入微任务阶段。此时之前注册的 promise1.then()、promise2.then() 和 promise3.then() 依次被执行,因此会依次输出它们的值:Frist Promise、Second Promise、Third Promise。
微任务清空后,进入下一轮事件循环,也就是宏任务阶段。第一个 setTimeout 执行,输出 下一把再相见,然后在其中又注册了一个新的 Promise,它的 .then() 回调会作为微任务加入当前轮的微任务队列,因此立即执行输出 Forth Promise。
接着第二个 setTimeout 被执行,输出 下下一把再相见。
总结:彻底搞懂 JavaScript 中的 Event Loop
-
JavaScript 是单线程语言,通过 **Event Loop(事件循环)**机制实现对异步任务的调度。
-
所有任务分为两类:同步任务和异步任务,异步任务又分为:
- 宏任务(如
setTimeout、setInterval、I/O等) - 微任务(如
Promise.then、queueMicrotask)
- 宏任务(如
-
每一轮事件循环会按照以下流程执行:
- 执行当前宏任务(包括整体代码
script) - 清空微任务队列(执行所有微任务)
- 开始下一轮宏任务
- 执行当前宏任务(包括整体代码
-
微任务的执行优先于下一个宏任务,是理解异步执行顺序的关键。
-
熟练掌握 Event Loop 能帮助我们解决诸如:
- Promise 链的执行时机
- 异步 UI 更新
- setTimeout 与 Promise 谁先执行等面试高频问题
-
浏览器与 Node.js 的事件循环有差异,但核心机制一致,都是围绕任务队列的调度与执行顺序。
🎯 一句话总结:
弄懂 Event Loop,是成为高级 JavaScript 工程师的必修课。