Chapter 01

Node.js 与 Bun:运行时对比与选择

深入理解两大运行时的架构差异,从事件循环到引擎内核,找到适合你项目的那一个

什么是 JavaScript 运行时

浏览器之外,JavaScript 需要一个"宿主环境"才能运行——这个环境就是运行时(Runtime)。运行时提供了 JavaScript 引擎之外的一切:文件系统访问、网络 I/O、进程管理、定时器等系统级 API。

Node.js 架构深度解析

Node.js 由 Ryan Dahl 于 2009 年创建,其核心设计思想是:用非阻塞 I/O 和事件驱动模型解决 C10K(同时处理 10000 个连接)问题。

事件循环的六个阶段

Node.js 的事件循环基于 libuv,每次"tick"按顺序经过以下阶段:

┌─────────────────────────────────────────────────────┐
│                    事件循环 (Event Loop)               │
│                                                     │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐          │
│  │  timers  │→ │ pending  │→ │   idle   │          │
│  │setTimeout│  │callbacks │  │ prepare  │          │
│  └──────────┘  └──────────┘  └──────────┘          │
│       ↑                            ↓                │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐          │
│  │  close   │← │  check   │← │   poll   │          │
│  │callbacks │  │setImmed. │  │  I/O等待 │          │
│  └──────────┘  └──────────┘  └──────────┘          │
│                                                     │
│  每个阶段之间:清空 nextTick 队列 和 微任务(Promise)队列   │
└─────────────────────────────────────────────────────┘
// 执行顺序示例
console.log('1 同步代码');

setTimeout(() => console.log('4 timers 阶段'), 0);

Promise.resolve().then(() => console.log('3 微任务队列'));

process.nextTick(() => console.log('2 nextTick (优先级最高)'));

// 输出顺序: 1 → 2 → 3 → 4

process.nextTick vs Promise.then:两者都在当前操作完成后、下一事件循环阶段前执行(微任务),但 nextTick 的优先级更高。滥用 nextTick 可能会导致 I/O 饿死——如果 nextTick 不断递归调用自身,poll 阶段永远无法执行。

libuv 线程池

虽然 Node.js 的 JavaScript 是单线程的,但 libuv 维护了一个默认大小为 4 的线程池,用于处理:

# 可通过环境变量调整线程池大小(最大 1024)
UV_THREADPOOL_SIZE=16 node server.js

Bun 的架构创新

Bun 由 Jarred Sumner 于 2021 年开始开发,2023 年发布 1.0 正式版。它不只是一个运行时,更是一个完整的 JavaScript 工具链

Bun 包含
  • JavaScript 运行时(JSC 引擎)
  • 包管理器(替代 npm/yarn/pnpm)
  • 打包器(替代 webpack/esbuild)
  • 测试运行器(替代 Jest/Vitest)
  • TypeScript/JSX 原生转译
Node.js 需要
  • 运行时(V8 引擎)
  • npm / pnpm / yarn(独立安装)
  • webpack / esbuild(独立安装)
  • Jest / Vitest(独立安装)
  • ts-node / tsx / esbuild(独立)

为什么选 JavaScriptCore 而非 V8

这是 Bun 最具争议的设计决策之一。JSC 相比 V8 的优势:

指标V8(Node.js)JSC(Bun)
冷启动时间较慢(更多预热优化)更快(低延迟优先)
内存占用较高更低
长期峰值性能极高(JIT 充分预热后)略低于 V8 峰值
适合场景长时间运行的服务短命令/边缘计算/开发工具

性能对比基准数据

以下数据来自 Bun 官方基准测试(2024 年,Apple M1 Pro),实际生产环境因业务逻辑不同会有差异:

场景Node.js 22Bun 1.x提升
HTTP Hello World(req/s)~65,000~120,000+84%
bun install(冷缓存)npm ~10s~1.5s6.7x
bun install(热缓存)npm ~5s~180ms27x
脚本启动(--version~70ms~5ms14x
SQLite 读取(100万行)better-sqlite3 ~0.8s内置 ~0.3s2.7x

基准测试的局限性:以上数据是"Hello World"级别的压测。真实业务中,数据库查询、复杂业务逻辑、外部 API 调用等才是性能瓶颈。对于大多数 CRUD 应用,两者性能差异完全感知不到。不要因为 Bun 快就抛弃 Node.js 的成熟生态。

TypeScript 原生支持

这是 Bun 最受开发者喜爱的特性之一——无需任何配置,直接执行 .ts 文件:

Node.js — 需要配置
# 方案1:ts-node
npm install -D ts-node typescript
npx ts-node server.ts

# 方案2:tsx(推荐)
npm install -D tsx
npx tsx server.ts

# 方案3:Node.js 22.6+ 实验性支持
node --experimental-strip-types server.ts
Bun — 开箱即用
# 直接运行,无需任何安装
bun run server.ts

# 也支持 JSX/TSX
bun run app.tsx

# 监视模式(类似 nodemon)
bun --watch run server.ts

Bun 的 TS 转译是"类型擦除":Bun 并不做类型检查,只是去掉类型注解执行。这意味着类型错误在运行时不会被发现——你仍然需要在 CI 流程中运行 tsc --noEmit 做类型检查。

Node.js 22+ 新特性

Node.js 并没有停滞不前。22.x LTS(2024 年 10 月起)带来了一系列重要更新:

// Node.js 22 内置测试(node:test)
import { test, describe } from 'node:test';
import assert from 'node:assert';

test('两数相加', () => {
  assert.strictEqual(1 + 1, 2);
});

安装与工具链配置

安装 Node.js(推荐使用版本管理器)

# 方案1:fnm(Fast Node Manager,Rust 编写,推荐)
curl -fsSL https://fnm.vercel.app/install | bash
fnm install 22
fnm use 22

# 方案2:nvm(Node Version Manager,最广泛)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 22
nvm use 22

# 验证
node --version  # v22.x.x
npm --version   # 10.x.x

安装 Bun

# macOS / Linux
curl -fsSL https://bun.sh/install | bash

# Windows(PowerShell)
irm bun.sh/install.ps1 | iex

# 通过 npm 安装(适合 CI 环境)
npm install -g bun

# 升级 Bun
bun upgrade

# 验证
bun --version  # 1.x.x

创建第一个 Bun 项目

# 初始化项目
mkdir my-backend && cd my-backend
bun init

# bun init 会生成:
# ├── index.ts       — 入口文件
# ├── package.json   — 项目配置
# ├── tsconfig.json  — TypeScript 配置
# └── .gitignore

# 运行
bun run index.ts

何时选择 Bun,何时选 Node.js

场景推荐理由
全新项目,追求开发体验Bun原生 TS、极速安装、开箱即用
成熟产品,稳定性优先Node.jsLTS 支持、生态最成熟
Cloudflare Workers / 边缘Bun / Hono低冷启动,Hono 专为边缘设计
企业级、合规要求Node.js经过大量生产验证
脚本工具 / CLIBun启动速度快,Shell 内置
依赖 native addon(.node 文件)Node.jsBun 的 Node-API 兼容性仍在完善

实用策略:用 bun 作为包管理器和开发运行器(bun installbun run dev),用 node 作为生产运行器。这样既享受 Bun 的开发速度,又保留 Node.js 的生产稳定性。很多项目正在采用这种混合策略。