Node.js 开发指南 / 第 1 章 · Node.js 入门与概述
第 1 章 · Node.js 入门与概述
1.1 Node.js 简史
Node.js 由 Ryan Dahl 于 2009 年 创建,最初目标是解决传统 Web 服务器(如 Apache)在高并发场景下的性能瓶颈。Ryan Dahl 发现,传统的"一个请求一个线程"模型在大量并发连接时会消耗大量内存和 CPU 资源。
关键里程碑
| 年份 | 事件 |
|---|---|
| 2009 | Node.js 首次发布,基于 V8 引擎 |
| 2010 | npm(Node Package Manager)诞生 |
| 2011 | Windows 支持正式发布 |
| 2014 | io.js 分叉项目启动(后合并回 Node.js) |
| 2015 | Node.js v4.0 发布,io.js 合并,Node.js Foundation 成立 |
| 2018 | Node.js 10 引入 fs.promises API |
| 2020 | Node.js 14 LTS,ES Modules 实验性支持 |
| 2022 | Node.js 18 LTS,内置 fetch、Web Streams |
| 2024 | Node.js 22 LTS,require(esm) 默认支持 |
| 2025 | Node.js 24 发布,持续增强 ESM 和性能 |
与 io.js 的合并
2014 年,部分核心开发者因不满 Node.js 的更新节奏,创建了 io.js 项目。2015 年,双方达成共识,合并为 Node.js v4.0,由 Node.js Foundation(后并入 OpenJS Foundation)管理。
1.2 V8 引擎
什么是 V8
V8 是 Google 开发的高性能 JavaScript 和 WebAssembly 引擎,最初为 Chrome 浏览器设计。Node.js 将 V8 引擎独立出来,使其可以在浏览器之外运行 JavaScript。
V8 的工作原理
JavaScript 源码
│
▼
解析器(Parser)
│
▼
抽象语法树(AST)
│
▼
解释器 Ignition → 字节码(Bytecode)
│
▼
编译器 TurboFan → 优化机器码(Optimized Machine Code)
│
▼
去优化(Deoptimization)← 类型假设失败时回退
V8 的关键特性
| 特性 | 说明 |
|---|---|
| JIT 编译 | Just-In-Time 编译,将热点代码编译为机器码 |
| 垃圾回收 | 分代式 GC(Scavenge + Mark-Sweep-Compact) |
| 隐藏类 | 为对象创建隐藏类以优化属性访问 |
| 内联缓存 | 缓存属性查找结果,加速重复访问 |
| WebAssembly | 原生支持 WebAssembly 执行 |
在 Node.js 中查看 V8 版本
# 查看 Node.js 和 V8 版本
node -p "process.versions.v8"
# 完整版本信息
node -p "JSON.stringify(process.versions, null, 2)"
输出示例:
{
"node": "22.12.0",
"v8": "12.4.254.21-node.22",
"uv": "1.49.2",
"zlib": "1.3.0.2-motley-7d7a05e",
"nghttp2": "1.61.0",
"napi": "9",
"llhttp": "9.2.1"
}
1.3 事件驱动与非阻塞 I/O
传统阻塞模型
传统的服务器(如 Apache)采用多线程阻塞 I/O 模型:
请求1 → 线程1 → [阻塞等待数据库] → 返回响应
请求2 → 线程2 → [阻塞等待文件读取] → 返回响应
请求3 → 线程3 → [阻塞等待网络请求] → 返回响应
...
请求N → 线程N → [阻塞等待] → 返回响应
问题:每个连接占用一个线程,线程切换开销大,内存消耗高。
Node.js 事件驱动模型
Node.js 采用单线程事件循环 + 非阻塞 I/O 模型:
请求1 → [注册回调] → 继续处理其他请求
请求2 → [注册回调] → 继续处理其他请求
请求3 → [注册回调] → 继续处理其他请求
I/O 完成 → 事件队列 → 事件循环取出 → 执行回调
单线程 vs 多线程对比
| 对比维度 | 单线程(Node.js) | 多线程(Java/Apache) |
|---|---|---|
| 内存占用 | 低(单进程) | 高(每线程 1-8MB 栈空间) |
| 上下文切换 | 无 | 开销大 |
| 并发模型 | 事件驱动 | 线程池 |
| CPU 密集型 | 不擅长 | 擅长 |
| I/O 密集型 | 非常擅长 | 一般 |
| 编程复杂度 | 低(回调/Promise) | 高(锁、同步) |
代码示例:非阻塞 I/O
const fs = require('fs');
console.log('1. 开始读取文件');
// 非阻塞:注册回调后立即继续执行
fs.readFile('/etc/hosts', 'utf8', (err, data) => {
if (err) throw err;
console.log('3. 文件读取完成');
});
console.log('2. 文件读取已发起,但不会阻塞');
// 输出顺序:
// 1. 开始读取文件
// 2. 文件读取已发起,但不会阻塞
// 3. 文件读取完成
阻塞 vs 非阻塞对比
const fs = require('fs');
// 阻塞方式(不推荐在服务器中使用)
console.time('sync');
const data1 = fs.readFileSync('/etc/hosts', 'utf8');
console.timeEnd('sync'); // 约 1-3ms
// 非阻塞方式
console.time('async');
fs.readFile('/etc/hosts', 'utf8', (err, data2) => {
console.timeEnd('async'); // 回调执行时记录
});
console.log('这里不会被阻塞');
1.4 适用场景与不适用场景
适用场景 ✅
| 场景 | 说明 | 典型框架/工具 |
|---|---|---|
| I/O 密集型 Web 应用 | API 服务器、微服务 | Express, Fastify, Koa |
| 实时应用 | 聊天、协作编辑、推送 | Socket.io, ws |
| API 网关 | 请求路由、负载均衡 | Express, Kong |
| 流式处理 | 音视频转码、数据管道 | ffmpeg, pipeline |
| CLI 工具 | 命令行工具、脚本 | Commander, Inquirer |
| Serverless | 云函数、边缘计算 | AWS Lambda, Vercel |
| 前端构建工具 | 打包、编译、Lint | Webpack, Vite, ESLint |
| 爬虫与数据采集 | 网页抓取、数据处理 | Puppeteer, Cheerio |
不太适用的场景 ⚠️
| 场景 | 原因 | 替代方案 |
|---|---|---|
| CPU 密集型计算 | 单线程会阻塞事件循环 | Go, Rust, Python (NumPy) |
| 大型科学计算 | V8 内存限制 | C++, Fortran |
| 系统级编程 | 缺少底层访问 | Rust, C |
解决 CPU 密集型问题
Node.js 也提供了应对 CPU 密集型任务的方案:
// 方案 1:Worker Threads(Node.js 10.5+)
const { Worker, isMainThread, parentPort } = require('worker_threads');
if (isMainThread) {
// 主线程
const worker = new Worker(__filename);
worker.on('message', (result) => {
console.log('计算结果:', result);
});
worker.postMessage({ numbers: [1, 2, 3, 4, 5] });
} else {
// 工作线程
parentPort.on('message', ({ numbers }) => {
const sum = numbers.reduce((a, b) => a + b, 0);
parentPort.postMessage(sum);
});
}
// 方案 2:Cluster 模块(利用多核 CPU)
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isPrimary) {
console.log(`主进程 ${process.pid} 正在运行`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker) => {
console.log(`工作进程 ${worker.process.pid} 已退出`);
});
} else {
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello World\n');
}).listen(8000);
console.log(`工作进程 ${process.pid} 已启动`);
}
1.5 Node.js 架构全景
┌─────────────────────────────────────────────────┐
│ 应用代码 │
│ (JavaScript / TypeScript) │
├─────────────────────────────────────────────────┤
│ Node.js 标准库 │
│ fs http net crypto stream events ... │
├─────────────────────────────────────────────────┤
│ Node.js 绑定层(Bindings) │
│ (C++ 与 JavaScript 桥接) │
├──────────────────────┬──────────────────────────┤
│ V8 引擎 │ libuv │
│ (JS 执行 & GC) │ (异步 I/O 事件循环) │
├──────────────────────┴──────────────────────────┤
│ 操作系统 │
│ (文件系统 / 网络 / 进程) │
└─────────────────────────────────────────────────┘
核心组件说明
| 组件 | 职责 |
|---|---|
| V8 | 解析和执行 JavaScript 代码 |
| libuv | 跨平台异步 I/O 库,实现事件循环和线程池 |
| llhttp | 高性能 HTTP 解析器 |
| c-ares | 异步 DNS 解析 |
| OpenSSL | TLS/SSL 加密支持 |
| zlib | 数据压缩 |
1.6 与其他运行时的对比
| 特性 | Node.js | Deno | Bun |
|---|---|---|---|
| JavaScript 引擎 | V8 | V8 | JavaScriptCore |
| 包管理 | npm/yarn/pnpm | URL 导入 / npm(2.0+) | 内置 bun install |
| TypeScript | 需要编译 | 原生支持 | 原生支持 |
| 安全模型 | 无沙箱 | 默认沙箱 | 无沙箱 |
| 兼容性 | 最成熟 | 部分兼容 | 大部分兼容 |
| 生态系统 | 最丰富 | 增长中 | 增长中 |
| 性能 | 优秀 | 优秀 | 极快(启动) |
注意事项
⚠️ 单线程不等于单进程:Node.js 的主线程是单线程的,但可以通过
cluster、worker_threads或子进程利用多核 CPU。
⚠️ 避免阻塞事件循环:同步 I/O(如
fs.readFileSync)、JSON.parse大对象、正则回溯等都会阻塞事件循环,影响所有并发请求。
⚠️ 错误处理很重要:单线程意味着未捕获的异常会导致整个进程崩溃,务必做好错误处理。
业务场景
- 电商平台 API 服务:高并发商品查询、订单处理,I/O 密集型场景非常适合 Node.js
- 实时聊天应用:WebSocket 长连接,事件驱动模型天然适合
- BFF(Backend For Frontend):为前端聚合多个微服务的数据
- CI/CD 工具链:ESLint、Prettier、Vite 等前端工具链均基于 Node.js
扩展阅读
- Node.js 官方文档
- V8 官方博客
- libuv 官方文档
- Node.js 设计模式 — 深入理解异步设计模式
- Ryan Dahl: 关于 Node.js 的 10 个遗憾 — Node.js 创始人的反思演讲
下一章:第 2 章 · 安装与环境配置 — 学习使用 nvm 管理 Node.js 版本,配置开发环境。