event loop 事件循环
什么是事件循环?
事件循环是 JavaScript 运行时的一个核心机制,它管理着代码的执行顺序。它是一种机制,用于处理异步操作,事件循环的核心是一个循环,它不断地检查调用栈和任务队列,以确保代码按照正确的顺序执行。
JavaScript 的单线程本质
JavaScript 被设计为单线程语言,这意味着它只有一个调用栈,一次只能执行一段代码。这听起来像是一个限制,但正是这种简单性让 JavaScript 如此易于使用。
console.log('开始'); // 1
setTimeout(() => {
console.log('定时器回调'); // 3
}, 1000);
console.log('结束'); // 2
// 输出顺序:
// 开始
// 结束
// 定时器回调
事件循环的组成部分
1. 调用栈(Call Stack)
调用栈是 JavaScript 执行代码的地方。当函数被调用时,它会被推入栈顶;当函数返回时,它会从栈顶弹出。
function first() {
console.log('第一个函数');
second();
}
function second() {
console.log('第二个函数');
}
first();
2. 任务队列(Task Queue)
任务队列(也称为宏任务队列)存储着待处理的任务,如:
setTimeout和setInterval回调- I/O 操作
- UI 渲染
- 事件处理程序
3. 微任务队列(Microtask Queue)
微任务队列具有更高的优先级,包括:
- Promise 回调(
.then(),.catch(),.finally()) queueMicrotask()MutationObserver
事件循环的工作流程
事件循环遵循一个简单的循环:
- 执行调用栈中的同步代码
- 当调用栈为空时,检查微任务队列
- 执行所有微任务(直到微任务队列为空)
- 检查宏任务队列,执行一个宏任务
- 重复步骤 2-4
console.log('脚本开始'); // 同步代码
setTimeout(() => {
console.log('setTimeout'); // 宏任务
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise 1'); // 微任务
})
.then(() => {
console.log('Promise 2'); // 微任务
});
console.log('脚本结束'); // 同步代码
// 输出顺序:
// 脚本开始
// 脚本结束
// Promise 1
// Promise 2
// setTimeout
实际应用示例
场景 1:用户交互与数据获取
// 模拟用户点击和API调用
document.getElementById('button').addEventListener('click', () => {
console.log('点击事件处理'); // 宏任务
// 微任务优先于渲染
Promise.resolve().then(() => {
console.log('Promise 在点击中');
});
// 模拟API调用
fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log('获取到的数据:', data); // 微任务
});
});
console.log('脚本加载完成');
场景 2:动画性能优化
// 不推荐的写法 - 可能阻塞渲染
function processHeavyData() {
const data = Array.from({length: 100000}, (_, i) => i);
return data.map(x => Math.sqrt(x)).filter(x => x > 10);
}
// 推荐的写法 - 使用事件循环分块处理
function processInChunks(data, chunkSize = 1000) {
let index = 0;
function processChunk() {
const chunk = data.slice(index, index + chunkSize);
// 处理当前块
chunk.forEach(item => {
// 处理逻辑
});
index += chunkSize;
if (index < data.length) {
// 使用 setTimeout 让出控制权,允许渲染
setTimeout(processChunk, 0);
}
}
processChunk();
}
常见陷阱与最佳实践
陷阱 1:阻塞事件循环
// ❌ 避免 - 长时间运行的同步操作
function blockingOperation() {
const start = Date.now();
while (Date.now() - start < 5000) {
// 阻塞5秒
}
console.log('操作完成');
}
// ✅ 推荐 - 使用异步操作
async function nonBlockingOperation() {
await new Promise(resolve => setTimeout(resolve, 5000));
console.log('操作完成');
}
陷阱 2:微任务递归
// ❌ 可能导致微任务无限循环
function dangerousRecursion() {
Promise.resolve().then(dangerousRecursion);
}
// ✅ 使用 setImmediate 或 setTimeout 打破循环
function safeRecursion() {
Promise.resolve().then(() => {
setTimeout(safeRecursion, 0);
});
}
现代 JavaScript 中的事件循环
async/await 与事件循环
async function asyncExample() {
console.log('开始 async 函数');
await Promise.resolve();
console.log('在 await 之后'); // 微任务
const result = await fetch('/api/data');
console.log('数据获取完成'); // 微任务
}
console.log('脚本开始');
asyncExample();
console.log('脚本结束');
// 输出顺序:
// 脚本开始
// 开始 async 函数
// 脚本结束
// 在 await 之后
// 数据获取完成
调试技巧
1. 使用 console 理解执行顺序
console.log('同步 1');
setTimeout(() => console.log('宏任务 1'), 0);
Promise.resolve()
.then(() => console.log('微任务 1'))
.then(() => console.log('微任务 2'));
queueMicrotask(() => console.log('微任务 3'));
console.log('同步 2');
2. 性能监控
// 测量任务执行时间
const startTime = performance.now();
setTimeout(() => {
const endTime = performance.now();
console.log(`任务执行耗时: ${endTime - startTime}ms`);
}, 0);
执行顺序问题
网上很经典的面试题
async function async1 () {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2 () {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1()
new Promise (function (resolve) {
console.log('promise1')
resolve()
}).then (function () {
console.log('promise2')
})
console.log('script end')
输出结果
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
总结
理解 JavaScript 事件循环对于编写高效、响应迅速的应用程序至关重要。记住这些关键点:
- 同步代码首先执行
- 微任务在同步代码之后、渲染之前执行
- 宏任务在微任务之后执行
- 避免阻塞主线程
- 合理使用微任务和宏任务
掌握事件循环机制将帮助你写出更好的异步代码,避免常见的性能问题,并创建更流畅的用户体验。
希望这篇博客能帮助你更好地理解 JavaScript 的事件循环机制!如果你有任何问题或想法,欢迎在评论区讨论。
作者:读忆
来源:juejin.cn/post/7565766784159776809
来源:juejin.cn/post/7565766784159776809