Node.js 基础与事件循环

14 阅读3分钟

一、Node.js 是什么?

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境

一句话:让 JavaScript 跑在服务器端,不再只能写网页。

关键词

  • 不是语言,不是框架,是一个运行环境
  • 单线程:主线程只有一个(但底层用 libuv 做异步 I/O)
  • 事件驱动:通过事件循环处理并发
  • 非阻塞 I/O:读文件、查数据库不会卡住主线程

二、Node.js 优缺点

优点

优点说明
高并发事件驱动 + 非阻塞 I/O,轻松处理大量并发连接
前后端统一前后端都用 JS,降低切换成本
NPM 生态全球最大的包管理生态,开箱即用
轻量高效适合 I/O 密集型应用(API 服务、实时通信)
上手快前端开发者可以快速转后端

缺点

缺点说明
不适合 CPU 密集型单线程,大量计算会阻塞主线程
回调地狱早期大量回调嵌套(现在用 async/await 解决了)
单线程不够稳一个未捕获异常可能导致整个进程崩溃
弱类型JS 缺少类型检查(可用 TypeScript 弥补)

面试回答模板

Node.js 适合 I/O 密集型(API网关、实时聊天、文件处理),不适合 CPU 密集型(视频编码、大量数学计算)。可以用 Worker Threads 或子进程来处理 CPU 密集任务。

三、事件循环机制(Event Loop)

这是 Node.js 面试的必考题

为什么需要事件循环?

Node.js 是单线程的,但需要处理大量异步操作(读文件、网络请求、定时器)。事件循环就是它的"调度中心"。

事件循环的 6 个阶段

   ┌───────────────────────────┐
┌─→│         timers             │  ← setTimeout / setInterval 回调
│  └───────────┬───────────────┘
│  ┌───────────┴───────────────┐
│  │     pending callbacks     │  ← 系统级回调(TCP错误等)
│  └───────────┬───────────────┘
│  ┌───────────┴───────────────┐
│  │       idle, prepare       │  ← 内部使用
│  └───────────┬───────────────┘
│  ┌───────────┴───────────────┐
│  │          poll              │  ← I/O 回调(文件读写、网络请求)
│  └───────────┬───────────────┘
│  ┌───────────┴───────────────┐
│  │          check            │  ← setImmediate 回调
│  └───────────┬───────────────┘
│  ┌───────────┴───────────────┐
│  │      close callbacks      │  ← socket.on('close') 等
│  └───────────┬───────────────┘
└─────────────┘(循环)

各阶段做什么?

阶段执行内容
timers执行到期的 setTimeout / setInterval 回调
pending执行上一轮延迟的 I/O 回调
poll获取新的 I/O 事件,执行 I/O 回调。这是最重要的阶段
check执行 setImmediate 回调
close执行关闭事件回调,如 socket.on('close')

微任务 vs 宏任务

在 Node.js 中,每个阶段切换之间都会清空微任务队列:

类型包含优先级
微任务process.nextTickPromise.then高(每个阶段之间执行)
宏任务setTimeoutsetIntervalsetImmediate、I/O低(在对应阶段执行)

执行顺序process.nextTick > Promise.then > 宏任务

经典面试题

console.log('1');

setTimeout(() => {
  console.log('2');
}, 0);

setImmediate(() => {
  console.log('3');
});

Promise.resolve().then(() => {
  console.log('4');
});

process.nextTick(() => {
  console.log('5');
});

console.log('6');

输出1 → 6 → 5 → 4 → 2 → 3

解析

  1. 16 —— 同步代码,立即执行
  2. 5 —— process.nextTick 微任务,最高优先级
  3. 4 —— Promise.then 微任务,次高优先级
  4. 2 —— setTimeout 宏任务(timers 阶段)
  5. 3 —— setImmediate 宏任务(check 阶段)

注意:setTimeout(fn, 0)setImmediate 在主模块中的顺序不确定,但在 I/O 回调中 setImmediate 总是先执行。

四、高频面试题

Q1:Node.js 的单线程是什么意思?

JS 执行是单线程的,但 Node.js 底层并不是单线程。libuv 维护了一个线程池(默认 4 个线程)处理文件 I/O 等操作。网络 I/O 使用的是操作系统的异步机制(epoll/kqueue)。

Q2:Node.js 适用于什么场景?

  • RESTful API 服务
  • 实时应用(聊天室、协作工具)
  • 微服务网关
  • 服务端渲染(SSR)
  • CLI 工具
  • 流式数据处理

Q3:如何解决单线程 CPU 密集问题?

  • worker_threads —— Node.js 内置的工作线程
  • child_process —— 开子进程
  • cluster 模块 —— 多进程利用多核 CPU
  • 把计算密集任务交给 C++ 插件(N-API)