Skip to content

第 1 章:Node.js 概述与运行机制 ​

作为后端开发者,理解 Node.js 最好的方式,是先搞懂它「单线程却高效」的核心机制。这一章从运行原理讲起,为后面所有内容打地基。


1.1 Node.js 是什么 ​

Node.js 不是一门语言,也不是一个框架,而是一个 JavaScript 运行时——它把 Chrome 浏览器的 V8 引擎拿出来,加上了操作文件、网络、进程的能力,让 JavaScript 能在服务器端跑起来。

┌─────────────────────────────────────────────┐
│                 你的 JavaScript 代码          │
└──────────────────────┬──────────────────────┘
                       │
┌──────────────────────▼──────────────────────┐
│                Node.js 运行时                 │
│  ┌────────────┐  ┌────────────┐  ┌────────┐ │
│  │  V8 引擎    │  │  libuv     │  │ 内置模块 │ │
│  │ (执行 JS)   │  │(事件循环/异步)│  │ fs/http │ │
│  └────────────┘  └────────────┘  └────────┘ │
└──────────────────────┬──────────────────────┘
                       │
        ┌──────────────┼──────────────┐
        ▼              ▼              ▼
     文件系统         网络          进程/线程池

核心定位:Node.js 用 JavaScript 写后端,主打「高并发 I/O 密集」场景——API 网关、实时通信、代理转发、AI 服务编排这类「大量等待、少量计算」的任务,正是它的主场。


1.2 单线程,为什么还高效? ​

这是后端开发者最容易困惑的点。Java 一个请求一个线程,线程多了开销大;Node 只有一个主线程,却能扛住上万个并发连接。差别在于处理 I/O 的方式:

【传统多线程模型】一个连接 = 一个线程,线程阻塞等待 I/O

  请求1 ──► 线程1 ──阻塞等待DB──► 返回
  请求2 ──► 线程2 ──阻塞等待DB──► 返回
  请求3 ──► 线程3 ──阻塞等待DB──► 返回
  (线程切换开销大,1000 连接 = 1000 线程)

【Node 单线程异步模型】一个线程,I/O 交给系统,不阻塞

  请求1 ─┐
  请求2 ─┼──► 主线程(轻量处理)──► 发起异步 I/O ──► 系统线程池
  请求3 ─┘                                   │
                     ┌───────────────────────┘
                     ▼  I/O 完成,回调排队
                事件循环逐个执行回调 ──► 返回响应

关键结论:

  • Node 的「单线程」指的是执行你 JS 代码的那个主线程只有一个;
  • 耗时的 I/O(读文件、查数据库、发网络请求)交给 libuv 的线程池 / 操作系统异步去做,主线程不傻等;
  • I/O 完成后,回调函数进入队列,由事件循环逐个调用。

所以 Node 擅长「I/O 密集」:大量等待、少量 CPU 计算。反过来,CPU 密集(大量循环计算)会卡住唯一的主线程,那是 Node 的短板。


1.3 V8 引擎与 JIT 编译 ​

V8 负责把 JavaScript 源码变成机器码执行。它不预先编译,而是运行时即时编译(JIT),并在运行中根据热点不断优化:

JavaScript 源码
      │ 解析(Parser)
      ▼
   抽象语法树 AST
      │ 解释器 Ignition 生成字节码
      ▼
   字节码 Bytecode ──执行──► 结果
      │ 热点代码检测(反复执行的热函数)
      ▼
   优化编译器 TurboFan ──► 高度优化的机器码

不用深究 V8 内部,只需知道:频繁执行的代码会被 JIT 优化得接近编译语言速度,这是 Node 高性能的来源之一。


1.4 事件循环 Event Loop(核心) ​

事件循环是 Node 的「心脏」,理解它才能写出正确的异步代码。一句话概括:

事件循环不断从任务队列里取出回调执行,直到队列清空。

Node 的事件循环分几个阶段(phase),每个阶段处理一类回调:

   ┌───────────────────────────┐
   │        timers 阶段         │  setTimeout / setInterval 到期回调
   └───────────┬───────────────┘
               ▼
   ┌───────────────────────────┐
   │     pending callbacks     │  上轮遗留的 I/O 回调
   └───────────┬───────────────┘
               ▼
   ┌───────────────────────────┐
   │      idle / prepare       │  内部用,一般忽略
   └───────────┬───────────────┘
               ▼
   ┌───────────────────────────┐
   │         poll 阶段          │  ← 核心:执行 I/O 回调,无任务则等待
   └───────────┬───────────────┘
               ▼
   ┌───────────────────────────┐
   │         check 阶段         │  setImmediate 回调
   └───────────┬───────────────┘
               ▼
   ┌───────────────────────────┐
   │      close callbacks      │  socket 关闭等收尾回调
   └───────────┬───────────────┘
               ▼
         (一轮结束,回到 timers 开始下一轮)

最重要的两个阶段:

  • timers:执行到期的 setTimeout / setInterval;
  • poll:执行 I/O 回调(如文件读取完成、网络响应),如果没有待办任务,会在这里阻塞等待新事件。

1.5 微任务:process.nextTick 与 Promise ​

事件循环里还有一条「插队」规则——微任务(microtask)。Promise 的 .then 回调、process.nextTick 都是微任务,它们在当前阶段结束、下一阶段开始前优先执行:

执行栈中一个回调执行完
        │
        ▼
  ┌─────────────────┐
  │ 清空 nextTick 队列 │  ← process.nextTick(优先级最高)
  └────────┬────────┘
           ▼
  ┌─────────────────┐
  │  清空微任务队列    │  ← Promise.then / queueMicrotask
  └────────┬────────┘
           ▼
    回到事件循环下一个阶段

一个经典面试题,验证你是否理解:

javascript
console.log('1');                    // 同步,先打印

setTimeout(() => console.log('2'), 0); // 宏任务,进 timers 队列

Promise.resolve()
  .then(() => console.log('3'));      // 微任务,优先于宏任务

process.nextTick(() => console.log('4')); // nextTick,比 Promise 更优先

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

记忆口诀:同步先行,nextTick 插队最前,Promise 微任务其次,setTimeout 宏任务最后。


1.6 本章小结 ​

要点说明
Node 是什么一个 JavaScript 运行时(V8 + libuv + 内置模块)
单线程异步主线程只有一个,I/O 交给系统异步处理,靠事件循环调度
擅长场景I/O 密集(API 网关、代理、实时、AI 编排)
短板CPU 密集计算会阻塞主线程
事件循环分阶段循环执行回调,核心是 timers 和 poll
微任务nextTick > Promise > 宏任务,掌握执行顺序是异步基础

下一章预告 ​

理解了运行机制,接下来看代码怎么组织——CommonJS 和 ESM 两种模块规范有什么区别?npm 怎么管理依赖?

👉 第 2 章:模块系统与 npm

📖本文阅读--次|📊全站访问--次|👥访客--人