JavaScript 事件循环 (Event Loop) 完整教程目录
什么是事件循环
事件循环 (Event Loop) 是 JavaScript 运行时环境的核心机制,它负责协调代码执行、事件处理和渲染等操作。尽管 JavaScript 是单线程语言,但通过事件循环机制,它能够实现非阻塞的异步操作。
核心概念
-
JavaScript 引擎是单线程的
-
事件循环使得异步操作成为可能
-
通过任务队列管理待执行的代码
-
区分宏任务和微任务的执行优先级
JavaScript 单线程模型

为什么是单线程?
-
简化编程模型:避免多线程的复杂性和竞态条件
-
DOM 操作安全:防止多线程同时操作 DOM 造成冲突
-
内存管理简单:单线程环境下的垃圾回收更容易实现
事件循环的组成部分
1. 执行栈 (Call Stack)

特点:
-
LIFO (后进先出) 结构
-
存储函数调用信息
-
栈为空时,事件循环才会处理任务队列
2. 堆内存 (Heap)
-
存储对象和变量
-
动态分配内存空间
-
垃圾回收器管理
3. 任务队列 (Task Queue)
分为两种类型:
-
宏任务队列 (Macro Task Queue)
-
微任务队列 (Micro Task Queue)
事件循环工作原理
完整流程图

执行顺序
-
执行同步代码:直接在执行栈中运行
-
清空微任务队列:执行所有微任务
-
执行一个宏任务:从宏任务队列取出一个任务
-
重复步骤 2-3:直到所有任务完成
详细步骤说明
// 示例代码执行流程
console.log('1'); // 同步代码,立即执行
setTimeout(() => {
console.log('2'); // 宏任务,加入宏任务队列
}, 0);
Promise.resolve().then(() => {
console.log('3'); // 微任务,加入微任务队列
});
console.log('4'); // 同步代码,立即执行
// 输出顺序:1, 4, 3, 2
执行分析:
-
执行
console.log('1')→ 输出 "1" -
setTimeout回调加入宏任务队列 -
Promise.then回调加入微任务队列 -
执行
console.log('4')→ 输出 "4" -
执行栈清空,处理微任务队列 → 输出 "3"
-
处理宏任务队列 → 输出 "2"
宏任务与微任务
对比表格
| 特性 | 宏任务 (Macro Task) | 微任务 (Micro Task) |
|---|---|---|
| 执行时机 | 每轮事件循环执行一个 | 每轮事件循环执行所有 |
| 优先级 | 低 | 高 |
| 常见类型 | setTimeout, setInterval, DOM事件 | Promise.then, async/await |
| 队列清空 | 执行一个后检查微任务 | 必须全部执行完 |
宏任务 (Macro Tasks)
// 常见的宏任务
setTimeout(() => console.log('setTimeout'), 0);
setInterval(() => console.log('setInterval'), 1000);
// DOM 事件
document.addEventListener('click', () => {
console.log('click event');
});
// HTTP 请求
fetch('/api/data').then(response => {
// 这个 then 是微任务
console.log('fetch response');
});
微任务 (Micro Tasks)
// Promise 相关
Promise.resolve().then(() => console.log('Promise.then'));
// async/await
async function asyncFunc() {
await Promise.resolve();
console.log('async/await');
}
// MutationObserver
const observer = new MutationObserver(() => {
console.log('DOM mutation');
});
// queueMicrotask (现代浏览器)
queueMicrotask(() => console.log('queueMicrotask'));
执行优先级示例
console.log('=== 开始 ===');
// 宏任务
setTimeout(() => console.log('宏任务 1'), 0);
setTimeout(() => console.log('宏任务 2'), 0);
// 微任务
Promise.resolve().then(() => console.log('微任务 1'));
Promise.resolve().then(() => console.log('微任务 2'));
// 同步代码
console.log('同步代码');
console.log('=== 结束 ===');
/* 输出顺序:
=== 开始 ===
同步代码
=== 结束 ===
微任务 1
微任务 2
宏任务 1
宏任务 2
*/
代码示例分析
示例 1:基础执行顺序
function example1() {
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
}
example1();
// 输出:1, 4, 3, 2
分析过程:
执行栈: [example1]
1. console.log('1') → 输出 "1"
2. setTimeout 回调 → 宏任务队列: [() => console.log('2')]
3. Promise.then 回调 → 微任务队列: [() => console.log('3')]
4. console.log('4') → 输出 "4"
执行栈: []
5. 处理微任务队列 → 输出 "3"
6. 处理宏任务队列 → 输出 "2"
示例 2:嵌套异步操作
function example2() {
console.log('开始');
setTimeout(() => {
console.log('宏任务 1');
Promise.resolve().then(() => console.log('宏任务中的微任务'));
}, 0);
Promise.resolve().then(() => {
console.log('微任务 1');
setTimeout(() => console.log('微任务中的宏任务'), 0);
});
console.log('结束');
}
example2();
/* 输出:
开始
结束
微任务 1
宏任务 1
宏任务中的微任务
微任务中的宏任务
*/
示例 3:复杂的 async/await
async function example3() {
console.log('async 开始');
await Promise.resolve();
console.log('await 后');
setTimeout(() => console.log('async 中的宏任务'), 0);
return 'async 返回值';
}
console.log('全局开始');
example3().then(result => {
console.log('async 结果:', result);
});
Promise.resolve().then(() => console.log('全局微任务'));
console.log('全局结束');
/* 输出:
全局开始
async 开始
全局结束
await 后
全局微任务
async 结果: async 返回值
async 中的宏任务
*/
常见面试题
题目 1:经典执行顺序
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
});
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5');
});
});
process.nextTick(function() {
console.log('6');
});
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8');
});
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
});
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12');
});
});
// Node.js 环境输出:1, 7, 6, 8, 2, 4, 3, 5, 9, 11, 10, 12
题目 2:Promise 链式调用
Promise.resolve().then(() => {
console.log('Promise1');
setTimeout(() => console.log('setTimeout1'), 0);
}).then(() => {
console.log('Promise2');
});
setTimeout(() => {
console.log('setTimeout2');
Promise.resolve().then(() => console.log('Promise3'));
}, 0);
// 输出:Promise1, Promise2, setTimeout2, Promise3, setTimeout1
题目 3:async/await 陷阱
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');
/* 输出:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/
最佳实践
1. 避免阻塞主线程
// ❌ 错误:长时间运行的同步代码
function badExample() {
for (let i = 0; i < 1000000000; i++) {
// 大量计算,阻塞主线程
}
}
// ✅ 正确:分批处理
function goodExample() {
let i = 0;
function processChunk() {
let count = 0;
while (count < 100000 && i < 1000000000) {
// 处理一小批数据
i++;
count++;
}
if (i < 1000000000) {
setTimeout(processChunk, 0); // 让出控制权
}
}
processChunk();
}
2. 合理使用微任务
// ✅ 使用微任务进行状态同步
function updateState(newState) {
this.state = newState;
// 使用微任务确保状态更新后立即执行
Promise.resolve().then(() => {
this.notifyObservers();
});
}
// ✅ 使用 queueMicrotask
function scheduleUpdate() {
queueMicrotask(() => {
// 在下一个微任务中执行
performUpdate();
});
}
3. 错误处理
// ✅ 正确的异步错误处理
async function handleAsyncOperation() {
try {
const result = await fetchData();
return result;
} catch (error) {
console.error('异步操作失败:', error);
throw error;
}
}
// ✅ Promise 错误处理
Promise.resolve()
.then(() => riskyOperation())
.catch(error => {
console.error('Promise 链中的错误:', error);
});
4. 性能优化
// ✅ 批量 DOM 操作
function batchDOMUpdates() {
// 使用 requestAnimationFrame 进行批量更新
requestAnimationFrame(() => {
// 批量执行 DOM 操作
elements.forEach(el => {
el.style.transform = 'translateX(100px)';
});
});
}
// ✅ 防抖和节流
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
总结
关键要点
-
JavaScript 是单线程的,但通过事件循环实现异步
-
微任务优先级高于宏任务
-
每轮事件循环:执行所有微任务 → 执行一个宏任务
-
理解执行顺序对于编写可预测的异步代码至关重要
记忆口诀
同步代码先执行完,
微任务队列全清空,
宏任务一个一个来,
循环往复不停歇。
调试技巧
-
使用
console.log跟踪执行顺序 -
利用浏览器开发者工具的 Performance 面板
-
理解 Call Stack 和 Task Queue 的状态变化
通过深入理解事件循环机制,你将能够:
-
编写更高效的异步代码
-
避免常见的异步编程陷阱
-
优化应用程序性能
-
在面试中自信地回答相关问题
更多推荐


所有评论(0)