【TypeScript】事件循环
文章目录
- 一、事件循环
- 1、相关基础
- 1.1 概念
- 1.2 关系
- 2、事件循环
- 2.1 阶段
- 2.2 分类
- 2.3 流程
一、事件循环
1、相关基础
1.1 概念
- 并发(
Concurrency
):指在同一时段内处理多个任务,但不一定同时执行。指多个任务在逻辑上交替执行的能力。即使只有一个执行线程,通过任务切换(如暂停一个任务去处理另一个任务),也能实现 “同时处理多个任务” 的效果。 - 并行(
Parallelism
):指在完全相同的时间段内,多个任务同时执行,通常需要多核处理器。即指多个任务在物理上同时执行,需要依赖多线程或多进程。每个任务在独立的执行单元(如 CPU 核心)中运行,互不干扰。 - 事件循环(
Event Loop
):TypeScript 是单线程的,单核处理,本身无法实现并行,但事件循环是处理非阻塞 I/O 操作的核心机制,使得单线程能够高效处理多个并发请求。- 同步代码直接在执行栈(主线程:当代码被执行时,同步任务会被压入调用栈中,按照后进先出(
LIFO
)的原则依次执行)中顺序执行; - 异步任务(如
setTimeout
、Promise
等)会被挂起,其回调函数放入任务队列(Task Queue
); - 当执行栈(
Call Stack
)为空时,事件循环从队列中取出回调函数执行,形成 “交替处理多个任务” 的并发效果。
- 同步代码直接在执行栈(主线程:当代码被执行时,同步任务会被压入调用栈中,按照后进先出(
1.2 关系
- 事件循环是实现并发的基础。
console.log("start");
setTimeout(() => console.log("timeout"), 0);
console.log("end");
// 输出顺序:start → end → timeout
执行顺序:执行执行栈中的任务*console
.log("start")
,遇到**定时器,*异步执行,放入任务队列等待主线程执行完成再执行,继续执行执行栈中任务console
.log("end")
,执行栈为空,通过事件循环机制执行任务队列中的任务,即执行*console
*.log("timeout")
。
2、事件循环
2.1 阶段
事件循环分为多个阶段,每个阶段处理特定的任务。关键阶段如下:
- Timers:执行
setTimeout()
和setInterval()
的回调。 - I/O Callbacks:处理一些延迟的 I/O 回调。
- Idle, prepare:内部使用,不常见。
- Poll:检索新的 I/O 事件,执行与 I/O 相关的回调。
- Check:执行
setImmediate()
回调。 - Close Callbacks:处理关闭的回调,如
socket.on('close', ...)
。
2.2 分类
事件循环将任务队列分为宏任务(Macro Task
) 和微任务(Micro Task
),优先级不同。
- 宏任务:顶层同步代码、
setTimeout
、setInterval
、网络请求、I/O操作等; - 微任务:
Promise.then/catch/finally
、async/await
、queueMicrotask
等。
执行规则:
- 执行完当前执行栈中的同步代码;
- 清空所有微任务(优先级高,全部执行完才会继续);
- 执行一个宏任务;
- 重复步骤 2-3。
这种机制确保了高优先级的异步任务(如 Promise 回调)能比宏任务更早执行,优化了并发场景下的任务调度。
2.3 流程
- 任务进入事件循环队列。
- 事件循环按照阶段顺序进行处理,每个阶段有自己的回调队列。
- 事件循环会在
poll
阶段等待新的事件到达,如果没有事件,会检查其他阶段的回调。 - 如果
setImmediate()
和setTimeout()
都存在,setImmediate()
在check
阶段先执行,而setTimeout()
在timers
阶段执行。
setTimeout(() => {console.log('Timeout callback');
}, 0);setImmediate(() => {console.log('Immediate callback');
});console.log('Main thread execution');// Main thread execution 先打印。
// setImmediate() 和 setTimeout() 的执行顺序取决于当前事件循环的状态,一般 setImmediate() 会先执行。
setTimeout(() => {console.log('Timeout callback');
}, 0);Promise.resolve().then(() => {console.log('Promise callback');
});console.log('Main thread execution');// 执行顺序:微任务优先级高于宏任务,会在当前阶段的回调结束后立即执行。
// Main thread execution 先打印。
// Promise callback
// Timeout callback
注意:而“宏任务在执行中” 的定义很明确:某个宏任务对应的代码正在执行栈中运行(比如顶层同步代码正在逐行执行,或 setTimeout 回调正在执行栈中跑逻辑)。只要执行栈中有代码在运行,就说明 “有宏任务在执行中”;反之,执行栈为空,就意味着 “当前没有宏任务在执行中”。
当这些同步逻辑全部跑完后,执行栈会被清空(没有任何代码在运行)—— 此时,“顶层同步代码” 这个宏任务已经执行完毕,没有新的宏任务被压入执行栈,所以处于 “无宏任务在执行中” 的状态,直接进入微任务执行阶段。
关键区别在于:“宏任务在队列中”≠“宏任务在执行中”。
- 宏任务队列只是 “待执行宏任务的存储容器”,队列中的宏任务只有被事件循环 “取出并压入执行栈” 后,才算 “开始执行”;
console.log('1: 同步代码'); // 同步代码,直接执行// 宏任务:setTimeout setTimeout(() => {console.log('4: setTimeout 宏任务'); }, 0);// 微任务:Promise.then Promise.resolve().then(() => {console.log('3: Promise.then 微任务');// 微任务中嵌套微任务Promise.resolve().then(() => {console.log('3.1: 嵌套的微任务');}); });console.log('2: 同步代码结束'); // 同步代码,继续执行// 1: 同步代码 → 2: 同步代码结束 → 3: Promise.then 微任务 // → 3.1: 嵌套的微任务 → 4: setTimeout 宏任务