JavaScript是一门单线程语言,但通过事件循环(Event Loop)机制,它能够处理异步操作,实现非阻塞的I/O模型。让我们深入了解Web浏览器中的事件循环机制。
事件循环的基本概念
事件循环主要由以下几个部分组成:
- 调用栈(Call Stack):用于存储正在执行的函数调用。
- 堆(Heap):用于存储对象。
- 任务队列(Task Queue):
- 宏任务队列(Macrotask Queue)
- 微任务队列(Microtask Queue)
事件循环的流程
让我们用一个流程图来说明事件循环的工作原理:
- 首先,执行调用栈中的同步代码。
- 当调用栈为空时,检查微任务队列。
- 执行所有微任务直到微任务队列为空。
- 从宏任务队列中取出一个任务执行。
- 重复步骤2-4,直到所有任务都执行完毕。
宏任务vs微任务
理解宏任务和微任务的区别对于掌握事件循环机制至关重要。
宏任务包括:
- script(整体代码)
- setTimeout/setInterval
- UI渲染
- I/O操作
- postMessage
- MessageChannel
微任务包括:
- Promise的then/catch/finally回调
- MutationObserver回调
- queueMicrotask()
实例分析
让我们通过一个例子来理解事件循环的执行过程:
1console.log('1');
2
3setTimeout(() => {
4 console.log('2');
5}, 0);
6
7Promise.resolve().then(() => {
8 console.log('3');
9});
10
11new Promise((resolve) => {
12 console.log('4');
13 resolve();
14}).then(() => {
15 console.log('5');
16});
17
18console.log('6');
执行顺序分析:
- 首先执行同步代码,输出:1, 4, 6
- 将setTimeout回调放入宏任务队列
- 将第一个Promise的then回调放入微任务队列
- 将第二个Promise的then回调放入微任务队列
- 同步代码执行完毕,检查微任务队列,依次执行微任务,输出:3, 5
- 微任务队列清空,执行下一个宏任务(setTimeout回调),输出:2
最终输出顺序:1, 4, 6, 3, 5, 2
async/await 在事件循环中的表现
首先,让我们看一个包含 async/await
的代码示例:
1console.log('1');
2
3async function asyncFunction() {
4 console.log('2');
5 await Promise.resolve();
6 console.log('3');
7 await Promise.resolve();
8 console.log('4');
9}
10
11setTimeout(() => {
12 console.log('5');
13}, 0);
14
15asyncFunction();
16
17new Promise((resolve) => {
18 console.log('6');
19 resolve();
20}).then(() => {
21 console.log('7');
22});
23
24console.log('8');
让我们分析这段代码的执行顺序:
- 首先执行同步代码:
- 输出 ‘1’
- 遇到
asyncFunction()
调用,进入函数内部 - 输出 ‘2’
- 遇到第一个
await
,将后续代码放入微任务队列,然后跳出asyncFunction
- 将
setTimeout
回调放入宏任务队列 - 执行 Promise 构造函数中的同步代码,输出 ‘6’
- 输出 ‘8’
- 同步代码执行完毕,检查微任务队列:
- 执行
asyncFunction
中第一个await
后的代码,输出 ‘3’ - 遇到第二个
await
,将后续代码再次放入微任务队列 - 执行 Promise 的
then
回调,输出 ‘7’
- 执行
- 当前微任务队列执行完毕,再次检查微任务队列:
- 执行
asyncFunction
中第二个await
后的代码,输出 ‘4’
- 执行
- 微任务队列清空,执行下一个宏任务:
- 执行
setTimeout
回调,输出 ‘5’
- 执行
最终输出顺序:1, 2, 6, 8, 3, 7, 4, 5
注意事项
- Promise构造函数中的代码是同步执行的。
- async/await是Promise的语法糖,await后面的代码相当于放在then方法的回调中。
- 每执行完一个宏任务后,都会检查并执行微任务队列中的所有任务。
结论
理解事件循环机制对于编写高效的异步JavaScript代码至关重要。通过合理安排宏任务和微任务,我们可以优化代码执行顺序,提高应用性能。在实际开发中,要注意区分同步任务、宏任务和微任务,以便更好地控制代码流程。下一篇我们一起看一下Node.js事件循环机制。
个人笔记记录 2021 ~ 2025