源本科技 | 码上会

ECMAScript 事件循环

2026/04/11
1
0

单线程执行模型

JavaScript 是一门单线程的编程语言,这意味着在 JS 引擎主线程中,同一时间只能执行一个任务,所有任务必须排队依次执行。

单线程的天然缺陷:若前一个任务耗时过长(如大量计算、死循环),会阻塞主线程,导致后续任务无法执行,最终引发程序假死、无响应的问题。为解决阻塞问题,JavaScript 设计了同步任务 + 异步任务的分工方案,结合 Event Loop 实现非阻塞的异步执行。

同步任务与异步任务

JavaScript 把所有待执行任务分为两类,由不同机制调度执行,保证主线程不被阻塞:

同步任务

  • 别称:非耗时任务

  • 执行位置:在主线程执行栈中排队执行

  • 执行规则:前一个任务执行完毕,才能执行后一个任务

  • 典型示例:普通代码逻辑、console.log、变量赋值、同步函数调用

异步任务

  • 别称:耗时任务

  • 执行方式:委托宿主环境(浏览器 / Node.js)执行,不占用 JS 主线程

  • 回调机制:异步任务完成后,宿主环境会将其回调函数加入任务队列,等待主线程空闲时执行

  • 典型示例:定时器、网络请求、文件 I/O、Promise 回调

事件循环执行流程

Event Loop 是 JavaScript 主线程、宿主环境、任务队列的协作机制,核心执行步骤:

  1. 主线程自上而下执行所有同步任务,放入执行栈中依次运行

  2. 遇到异步任务,立即委托给宿主环境处理,主线程不等待,继续执行后续同步代码

  3. 宿主环境完成异步操作后,将对应的回调函数加入任务队列

  4. 主线程执行栈清空(同步任务全部执行完毕),循环读取任务队列中的回调函数,放入执行栈执行

  5. 主线程无限重复步骤 4,这个循环执行的机制就是 Event Loop(事件循环)

微任务和宏任务

为了更精细地调度异步任务,提升程序响应性,JavaScript 将异步任务划分为微任务宏任务两种队列,二者拥有严格的执行优先级

核心定义与分类

微任务

  • 优先级:高于宏任务

  • 执行时机:当前执行栈清空后,立即执行,会清空所有微任务再进入下一个阶段

  • 常见任务:Promise.then / catch / finallyprocess.nextTick(Node.js 特有)、MutationObserver(浏览器特有)

宏任务

  • 优先级:低于微任务

  • 执行时机:等待当前所有同步任务 + 微任务执行完毕后执行

  • 常见任务:setTimeoutsetInterval、文件 I/O 操作、网络请求、UI 渲染(浏览器)

执行优先级

同步任务 > 所有微任务 > 宏任务 每一轮事件循环中,必须先清空微任务队列,再执行下一个宏任务。

// 导入 then-fs 模块
import thenFs from "then-fs";

// 同步任务
console.log("A");

// 宏任务(文件 I/O),完成后 .then 注册微任务
thenFs.readFile("./files/1.txt", "utf8").then((data) => {
  console.log("B");
});

// 宏任务(定时器),回调加入宏任务队列
setTimeout(() => {
  console.log("C");
}, 0);

// 同步任务
console.log("D");

执行顺序

  1. 执行同步任务:按照代码顺序,依次打印 AD

  2. 执行所有微任务:文件读取完成后,Promise.then 微任务执行,打印 B

  3. 执行宏任务:微任务队列清空后,执行定时器宏任务,打印 C

最终输出

A
D
B
C
  1. Event Loop 是 JavaScript 实现非阻塞异步编程的核心机制,解决了单线程的阻塞问题

  2. 异步任务分为微任务和宏任务,执行优先级:同步任务 > 微任务 > 宏任务

  3. 微任务会在当前任务结束后立即执行,宏任务必须等待所有微任务执行完毕

  4. Promise 系列回调属于微任务,定时器、文件 I/O、网络请求属于宏任务


总结

  1. JavaScript 单线程特性决定了必须依靠 Event Loop 处理异步任务

  2. 同步任务优先执行,异步任务由宿主环境托管,完成后进入任务队列

  3. 微任务优先级高于宏任务,是理解异步代码执行顺序的关键