JavaScript 的事件循环 (Event Loop),读完你就明白了一文带你读懂什么是事件循环,事件循环是 Jav
JavaScript 是一种单线程的编程语言,这意味着它一次只能执行一个任务。为了管理复杂的异步操作,如事件处理、定时器、网络请求等,JavaScript 依赖于事件循环 (Event Loop) 来调度任务,确保非阻塞的执行。本文将详细介绍 JavaScript 的事件循环机制,解释其工作原理,并探讨如何在实际开发中应用事件循环。
一、什么是事件循环?
事件循环是 JavaScript 中处理异步操作的核心机制。它的主要职责是监控调用栈 (Call Stack
) 和任务队列 (Task Queue
),并协调两者的工作,以确保 JavaScript 程序能够高效地处理异步任务,而不会阻塞主线程。
二、事件循环的工作原理
要理解事件循环的工作原理,首先需要了解以下几个关键概念:
1. 调用栈 (Call Stack)
调用栈是一种 LIFO(后进先出)结构,用于存储和跟踪函数的调用。每当一个函数被调用时,它会被压入栈顶,函数执行完成后,函数就会从栈顶弹出。
function foo() {
console.log("foo");
}
function bar() {
foo();
console.log("bar");
}
bar();
在这个示例中,调用栈的变化如下:
bar()
被调用,bar
压入栈顶bar
调用了foo
,foo
压入栈顶foo
执行完成并从栈顶弹出。bar
执行完成并从栈顶弹出。
2. 任务队列 (Task Queue)
任务队列是一个 FIFO(先进先出)结构,用于存储异步任务的回调函数。当异步操作(如定时器、事件、网络请求等)完成后,其回调函数会被放入任务队列中,等待调用栈清空后再执行。
任务队列通常分为两类:
- 宏任务队列 (Macro Task Queue): 包括
setTimeout
、setInterval
、I/O
、script
等任务。 - 微任务队列 (Micro Task Queue): 包括
Promise.then
、process.nextTick
(Node.js 环境)等任务。
3. 事件循环 (Event Loop)
事件循环的核心工作就是检查调用栈是否为空,并在栈为空时,从任务队列中取出下一个任务并执行。如果任务队列中有微任务,事件循环会优先处理微任务队列中的任务,然后再处理宏任务队列中的任务。
事件循环的步骤:
- 1、检查调用栈是否为空。
- 2、如果调用栈为空,检查微任务队列是否有任务,如果有,则依次执行。
- 3、微任务队列为空后,从宏任务队列中取出第一个任务,并将其回调函数压入调用栈执行。
- 4、重复上述步骤。
三、事件循环的具体示例
通过以下几个示例,深入理解事件循环的工作机制。
1. 基本示例
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
执行顺序分析:
- 1、
console.log('Start')
被执行,输出 "Start"。 - 2、
setTimeout
的回调被注册,加入宏任务队列。 - 3、
Promise.resolve().then
的回调被注册,加入微任务队列。 - 4、
console.log('End')
被执行,输出 "End"。 - 5、调用栈为空,事件循环检查微任务队列,执行
Promise.then
回调,输出 "Promise"。 - 6、微任务队列为空,事件循环从宏任务队列中取出
setTimeout
回调并执行,输出 "Timeout"。
最终输出顺序为:"Start" -> "End" -> "Promise" -> "Timeout"。
2. 微任务优先于宏任务
setTimeout(() => {
console.log("Macro Task 1");
}, 0);
Promise.resolve()
.then(() => {
console.log("Micro Task 1");
})
.then(() => {
console.log("Micro Task 2");
});
setTimeout(() => {
console.log("Macro Task 2");
}, 0);
执行顺序分析:
- 1、两个
setTimeout
回调被加入宏任务队列。 - 2、
Promise.then
回调被加入微任务队列。 - 3、 调用栈清空后,事件循环优先处理微任务队列,依次执行 "Micro Task 1" 和 "Micro Task 2"。
- 4、微任务队列为空后,事件循环依次处理宏任务队列中的任务,输出 "Macro Task 1" 和 "Macro Task 2"。
最终输出顺序为:"Micro Task 1" -> "Micro Task 2" -> "Macro Task 1" -> "Macro Task 2"。
3. 异步任务的嵌套
console.log("Start");
setTimeout(() => {
console.log("Timeout 1");
Promise.resolve().then(() => {
console.log("Promise 1");
});
}, 0);
Promise.resolve().then(() => {
console.log("Promise 2");
setTimeout(() => {
console.log("Timeout 2");
}, 0);
});
console.log("End");
执行顺序分析:
- 1、输出 "Start" 和 "End"。
- 2、事件循环检查微任务队列,执行 "Promise 2"。
- 3、"Promise 2" 的
then
回调中注册了一个宏任务Timeout 2
。 - 4、处理宏任务队列,执行 "Timeout 1"。
- 5、"Timeout 1" 的回调中注册了一个微任务 "Promise 1"。
- 6、处理微任务 "Promise 1"。
- 7、最后处理宏任务 "Timeout 2"。
最终输出顺序为:"Start" -> "End" -> "Promise 2" -> "Timeout 1" -> "Promise 1" -> "Timeout 2"。
四、事件循环在实际开发中的应用
在实际开发中,事件循环的理解和应用对处理异步任务、优化性能以及避免常见错误至关重要。
1. 避免 UI 阻塞
在浏览器环境中,JavaScript 在主线程上运行,这意味着长时间的同步操作会阻塞 UI 渲染,导致页面卡顿。通过将耗时操作放入异步任务中,可以避免阻塞主线程。
function heavyComputation() {
// 假设这是一个耗时的同步操作
let sum = 0;
for (let i = 0; i < 1e9; i++) {
sum += i;
}
console.log(sum);
}
setTimeout(heavyComputation, 0);
console.log("UI is still responsive");
通过将 heavyComputation
放入 setTimeout
中,确保了 UI 的响应性。
2. 正确处理异步操作
在处理异步操作时,理解事件循环有助于正确处理任务的执行顺序,避免一些常见的陷阱,例如错误地假设异步任务会按照特定顺序执行。
function asyncTask() {
setTimeout(() => console.log("Task 1"), 0);
Promise.resolve().then(() => console.log("Task 2"));
console.log("Task 3");
}
asyncTask();
输出顺序为:"Task 3" -> "Task 2" -> "Task 1"。理解事件循环可以帮助我们正确预测和调试这种异步行为。
3. 优化性能与用户体验
事件循环的机制可以用来优化性能。例如,分割长时间运行的任务,使得主线程能够更频繁地处理用户交互和更新 UI。
function processLargeArray(arr) {
if (arr.length === 0) return;
setTimeout(() => {
const chunk = arr.splice(0, 100);
chunk.forEach((item) => console.log(item));
processLargeArray(arr);
}, 0);
}
const largeArray = new Array(10000).fill(0).map((_, i) => i + 1);
processLargeArray(largeArray);
这种“分片”处理可以避免长时间运行的任务阻塞主线程,从而提升用户体验。
五、总结
JavaScript 的事件循环是理解异步编程的关键。通过事件循环,JavaScript 能够在单线程环境下高效地处理异步任务,而不阻塞主线程。本文详细介绍了事件循环的工作机制,并通过具体示例展示了它在实际开发中的应用。
掌握事件循环能够帮助我们编写更加高效、响应更快的代码,尤其是在处理复杂的异步逻辑时,理解事件循环是确保代码正确性的重要基础。
P.S.
本文首发于我的个人网站www.aifeir.com,若你觉得有所帮助,可以点个爱心鼓励一下,如果大家喜欢这篇文章,希望多多转发分享,感谢大家的阅读。
转载自:https://juejin.cn/post/7405523061891923979