JavaScript 事件循环(Event Loop)
JavaScript 事件循环(Event Loop)
什么是事件循环?
事件循环是JavaScript实现异步编程的核心机制。JavaScript是单线程语言,通过事件循环来处理异步操作,避免阻塞主线程。
详解:
JavaScript 在设计之初便是单线程,即指程序运行时,只有一个线程存在,同一时间只能做一件事。
为什么要这么设计,跟JavaScript的应用场景有关
JavaScript 初期作为一门浏览器脚本语言,通常用于操作 DOM ,如果是多线程,一个线程进行了删除 DOM ,另一个添加 DOM,此时浏览器该如何处理?
为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop)
图解:

核心概念
1. 调用栈(Call Stack)
- 存储函数执行上下文
- 遵循后进先出(LIFO)原则
- 同步代码在这里执行
2. 任务队列(Task Queue)
分为两种:
宏任务(Macro Task)
setTimeoutsetIntervalsetImmediate(Node.js)- I/O操作
- UI渲染
微任务(Micro Task)
Promise.then/catch/finallyasync/awaitMutationObserverprocess.nextTick(Node.js,优先级最高)
3. 执行顺序
- 执行同步代码(调用栈)
- 执行所有微任务
- 执行一个宏任务
- 执行所有微任务
- 重复步骤3-4
初等难度练习题
console.log(1)setTimeout(()=>{ console.log(2)},0)newPromise((resolve, reject)=>{ console.log('new Promise')resolve()}).then(()=>{ console.log('then')}) console.log(3)解题顺序
// 遇到 console.log(1) ,直接打印 1// 遇到定时器,属于新的宏任务,留着后面执行// 遇到 new Promise,这个是直接执行的,打印 'new Promise'// .then 属于微任务,放入微任务队列,后面再执行// 遇到 console.log(3) 直接打印 3// 好了本轮宏任务执行完毕,现在去微任务列表查看是否有微任务,发现 .then 的回调,执行它,打印 'then'// 当一次宏任务执行完,再去执行新的宏任务,这里就剩一个定时器的宏任务了,执行它,打印 2中等难度练习题
console.log('1');setTimeout(()=>{ console.log('2'); Promise.resolve().then(()=>{ console.log('3');});},0); Promise.resolve().then(()=>{ console.log('4');setTimeout(()=>{ console.log('5');},0);});newPromise((resolve)=>{ console.log('6');resolve();}).then(()=>{ console.log('7');});setTimeout(()=>{ console.log('8'); Promise.resolve().then(()=>{ console.log('9');});},0); console.log('10');题目要求
请写出上述代码的输出顺序,并解释原因。
答案解析
输出顺序:1 → 6 → 10 → 4 → 7 → 2 → 3 → 8 → 9 → 5
console.log('1');// 第一轮,同步代码。输出“1”setTimeout(()=>{// 第一轮,宏任务1加入队列 console.log('2');// 第三轮,执行宏任务1。输出“2” Promise.resolve().then(()=>{// 第三轮,立即执行微任务3。 console.log('3');// 第三轮,输出“3”});},0); Promise.resolve().then(()=>{// 第一轮, 微任务1加入队列 console.log('4');// 第二轮,执行微任务1。输出“4”setTimeout(()=>{// 第二轮,产生新的宏任务3 console.log('5');// 第五轮,执行宏任务3,输出“5”},0);});newPromise((resolve)=>{//第一轮, 同步。构造函数同步执行 console.log('6');//第一轮,输出“6”。resolve();}).then(()=>{// 第一轮,微任务2加入队列 console.log('7');// 第二轮,执行微任务2,输出“7”});setTimeout(()=>{// 第一轮,宏任务2加入队列。 console.log('8');// 第四轮,执行宏任务2。输出“8” Promise.resolve().then(()=>{// 第四轮,产生微任务4。 console.log('9');// 第四轮,立即执行微任务4,输出“9”});},0); console.log('10');//第一轮, 同步代码。输出“10”详细执行过程
第一轮(同步代码):
console.log('1')→ 输出 1setTimeout(...)→ 宏任务1加入队列Promise.resolve().then(...)→ 微任务1加入队列new Promise(...)构造函数同步执行 → 输出 6.then(...)→ 微任务2加入队列setTimeout(...)→ 宏任务2加入队列console.log('10')→ 输出 10
第二轮(清空微任务):
- 执行微任务1:输出 4,同时产生新的宏任务3
- 执行微任务2:输出 7
第三轮(执行宏任务1):
- 输出 2,产生微任务3
- 立即执行微任务3:输出 3
第四轮(执行宏任务2):
- 输出 8,产生微任务4
- 立即执行微任务4:输出 9
第五轮(执行宏任务3):
- 输出 5
关键点总结
- Promise构造函数是同步的,只有
.then是异步的 - 微任务优先级高于宏任务,每次宏任务后都会清空所有微任务
- setTimeout即使延迟为0,也会被放入宏任务队列
- 在宏任务中产生的微任务会在该宏任务执行完后立即执行
实际应用场景
1. 优化性能
// 使用微任务批量更新DOMlet updates =[];functionscheduleUpdate(data){ updates.push(data); Promise.resolve().then(()=>{// 批量处理所有更新processBatch(updates); updates =[];});}2. 确保执行顺序
asyncfunctionfetchData(){const data =awaitfetch('/api/data');// 这里的代码会在微任务中执行 console.log('数据获取完成');}3. 避免阻塞
functionheavyTask(){setTimeout(()=>{// 将耗时操作放入宏任务// 避免阻塞UI渲染performHeavyComputation();},0);}常见面试问题
- 微任务和宏任务的区别?
- 微任务在当前事件循环结束前执行,宏任务在下一个事件循环执行
- 微任务优先级更高
- async/await 的执行顺序?
await后面的代码相当于放在Promise.then中
- Node.js 中的 process.nextTick?
- 优先级高于所有微任务,在当前操作完成后立即执行