从零开始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 回调
相关推荐
sniper_fandc1 小时前
SpringBoot系列—入门
java·spring boot·后端
Piper蛋窝7 小时前
深入 Go 语言垃圾回收:从原理到内建类型 Slice、Map 的陷阱以及为何需要 strings.Builder
后端·go
六毛的毛10 小时前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack10 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
315356691310 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
uzong10 小时前
curl案例讲解
后端
一只叫煤球的猫11 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
大鸡腿同学12 小时前
身弱武修法:玄之又玄,奇妙之门
后端
会飞的鱼先生13 小时前
Node.js-path模块
node.js
轻语呢喃14 小时前
JavaScript :字符串模板——优雅编程的基石
前端·javascript·后端