从零开始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 回调
相关推荐
海梨花25 分钟前
【从零开始学习Redis】项目实战-黑马点评D2
java·数据库·redis·后端·缓存
bug菌28 分钟前
零基础也能做出AI应用?Trae是如何打破编程"高墙"的?
后端·ai编程·trae
Sammyyyyy32 分钟前
2025年,Javascript后端应该用 Bun、Node.js 还是 Deno?
开发语言·javascript·node.js
Java技术小馆34 分钟前
重构 Controller 的 7 个黄金法则
java·后端·面试
用户4099322502121 小时前
容器化部署FastAPI应用:如何让你的任务系统代码在云端跳舞?
后端·ai编程·trae
Java水解1 小时前
MySQL 亿级数据表平滑分表实践:基于时间分片的架构演进
后端·mysql
Neo2551 小时前
Spring 5.3.x 源码:invokeBeanFactoryPostProcessors()详解
后端
金銀銅鐵1 小时前
[Java] 以 IntStream 为例,浅析 Stream 的实现
java·后端
Neo2551 小时前
Spring 5.3.x 源码:refresh()方法
后端