从零开始Node之旅 —— 事件模型

1. JS的单线程特性

JavaScript 语言从诞生之初就被设计为单线程运行环境,这与其最初的应用场景 ------ 浏览器端脚本执行密切相关。Node.js 基于 V8 引擎构建,完全继承了 JavaScript 的单线程特性。从技术实现来看,JavaScript 引擎(如 V8 引擎)内部维护着一个调用栈(Call Stack),用于记录函数的调用关系。

1.1 同步任务与异步任务的区分

在单线程模型中,任务被分为两大类:同步任务和异步任务。同步任务会直接进入调用栈依次执行,例如普通的函数调用、算术运算等;而异步任务则不会立即进入调用栈,而是由对应的异步 API(如定时器、I/O 操作、事件监听)处理,当异步操作完成后,相关回调函数会被加入 ** 任务队列(Task Queue)** 等待执行。

javascript 复制代码
// 同步任务示例

console.log('开始执行同步任务');

function syncTask() {

for (let i = 0; i < 1000000; i++) {

// 模拟耗时操作

}

console.log('同步任务执行完毕');

}

syncTask();

// 异步任务示例

console.log('注册异步任务');

setTimeout(() => {

console.log('异步回调函数执行');

}, 0);

console.log('异步任务注册完成');

1.2 服务器IO问题

Node.js 通过以下机制突破了单线程的局限性,实现了高性能的服务器端应用:

(一)非阻塞 I/O 模型

Node.js 将所有 I/O 操作(如网络请求、文件读写)设计为异步非阻塞模式。当应用程序发起 I/O 请求时,Node.js 不会等待操作完成,而是立即返回并继续处理后续任务,I/O 操作的结果会通过事件循环通知应用程序。这种机制使得 Node.js 能够在单线程环境下同时处理数以万计的并发连接。

(二)与多线程的结合

尽管 Node.js 主线程是单线程的,但它通过底层的 libuv 库实现了与操作系统内核的交互,利用系统级的多线程处理 I/O 操作(如在 Windows 系统中使用 IOCP,在 Linux 系统中使用 epoll)。同时,Node.js 还提供了cluster模块,允许开发者创建多个工作进程,每个进程独立运行在不同的 CPU 核心上,从而充分利用多核 CPU 的性能。

(三)事件驱动架构

Node.js 的核心架构是事件驱动的,所有的异步操作最终都会转化为事件的触发和处理。应用程序通过监听事件(如connect、data、end等)来响应 I/O 操作的结果,事件处理器作为回调函数在事件循环中执行。这种架构使得 Node.js 的代码结构简洁高效,非常适合处理高并发、低延迟的网络应用。

其中,最最核心的,就是Node的事件驱动架构.

2. Node.js 事件驱动

2.1 浏览器的事件循环模型

JS主线程只有一个调用栈,其事件模型是通过调用栈 + 任务队列 的方式实现的。

浏览器通过调用栈管理函数的执行上下文,确保代码按顺序同步执行。遇到异步任务时,会将其放入任务队列,之后继续执行同步代码。

浏览器将异步任务的回调函数分为两类队列:宏任务队列(Macro Task Queue)和微任务队列(Micro Task Queue)。两类队列的执行优先级不同(微任务先于宏任务执行),直接影响任务的调度顺序。

2.2 Node.js 事件驱动模型

Node.js 事件驱动将宏任务队列(Macro Task Queue)进一步细化为6个阶段,6个阶段的异步回调任务会按照顺序执行:

  1. timers :处理 setTimeout/setInterval 的回调。
  2. I/O callbacks:(老IO事件,用户一般不使用)。
  3. idle, prepare:内部阶段,用户代码一般不参与。
  4. poll:获取新的 I/O 事件,处理未执行的回调。
  5. check :处理 setImmediate 的回调。
  6. close callbacks :处理 socket.destroy() 等关闭事件的回调。

2.3 Node.js 微任务

1. Promise 的回调(then/catch/finally

Promise 的 .then().catch().finally() 回调会被加入微任务队列。这是最常见的微任务类型。

javascript 复制代码
console.log('start'); 
Promise.resolve() 
    .then(() => { console.log('Promise then 回调'); // 微任务 }) 
    .catch(() => {}) 
    .finally(() => { console.log('Promise finally 回调'); // 微任务(在 then 之后执行) }); 
console.log('end');

输出:

javascript 复制代码
start 
end 
Promise then 回调 
Promise finally 回调

2. async/await 语法糖

async方法中的await关键词会把await之后的所有内容打包为一个Promise放入微任务队列。

3. ### process.nextTick:更高优先级的 "伪微任务"

Node.js 基于自身需求扩展了微任务类型,最典型的是 process.nextTick,其优先级高于所有 ES 标准微任务

process.nextTick 是 Node.js 特有的异步控制工具,其回调会被加入 nextTick 队列在当前同步代码执行完毕后,nextTick 队列 会优先于微任务队列执行(即使微任务队列已有任务)。

javascript 复制代码
console.log('start');

process.nextTick(() => {
  console.log('process.nextTick 回调'); // 优先于微任务
});

Promise.resolve().then(() => {
  console.log('Promise then 回调'); // 微任务
});

console.log('end');

输出:

arduino 复制代码
start
end
process.nextTick 回调
Promise then 回调
相关推荐
Mr Aokey2 小时前
Spring MVC参数绑定终极手册:单&多参/对象/集合/JSON/文件上传精讲
java·后端·spring
avoidaily3 小时前
使用Node.js分片上传大文件到阿里云OSS
阿里云·node.js·云计算
xd000023 小时前
8.axios Http网络请求库(1)
node.js
地藏Kelvin3 小时前
Spring Ai 从Demo到搭建套壳项目(二)实现deepseek+MCP client让高德生成昆明游玩4天攻略
人工智能·spring boot·后端
孟孟~3 小时前
npm run dev 报错:Error: error:0308010C:digital envelope routines::unsupported
前端·npm·node.js
孟孟~3 小时前
npm install 报错:npm error: ...node_modules\deasync npm error command failed
前端·npm·node.js
菠萝013 小时前
共识算法Raft系列(1)——什么是Raft?
c++·后端·算法·区块链·共识算法
长勺3 小时前
Spring中@Primary注解的作用与使用
java·后端·spring
小奏技术4 小时前
基于 Spring AI 和 MCP:用自然语言查询 RocketMQ 消息
后端·aigc·mcp
编程轨迹5 小时前
面试官:如何在 Java 中读取和解析 JSON 文件
后端