跳到主要内容

Node.js 基础

问题

Node.js 是什么?它有哪些特点和适用场景?

答案

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,用于在服务端运行 JavaScript 代码。


核心特点

1. 单线程 + 事件驱动

// Node.js 使用单线程处理请求
// 但通过事件循环实现高并发
import { createServer } from 'http';

const server = createServer((req, res) => {
// 每个请求都在同一个线程处理
// 但 I/O 操作是异步的,不会阻塞
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World\n');
});

server.listen(3000);
console.log('Server running at http://localhost:3000/');

2. 非阻塞 I/O

import { readFile } from 'fs';

console.log('开始读取文件');

// 异步读取,不阻塞后续代码
readFile('large-file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log('文件读取完成');
});

console.log('继续执行其他代码'); // 这行先执行

// 输出顺序:
// 开始读取文件
// 继续执行其他代码
// 文件读取完成

3. V8 引擎

  • JIT 编译:将 JavaScript 编译为机器码执行
  • 垃圾回收:自动内存管理
  • 持续优化:热点代码优化
// V8 的隐藏类优化
// 好的写法:对象形状一致
class Point {
x: number;
y: number;

constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}

// 避免:动态添加属性改变对象形状
const obj: Record<string, number> = {};
obj.a = 1; // 改变形状
obj.b = 2; // 再次改变形状

4. 跨平台

Node.js 可以运行在:

  • Windows
  • macOS
  • Linux
  • 其他 Unix 系统

架构组成

组件作用
V8JavaScript 引擎,编译执行 JS 代码
libuv跨平台异步 I/O 库,实现事件循环
Bindings连接 JavaScript 和 C++ 代码
核心模块fs、http、net、path 等内置模块

适用场景

适合

场景说明
I/O 密集型文件操作、网络请求、数据库查询
实时应用聊天、协作工具、在线游戏
API 服务RESTful API、GraphQL 服务
微服务轻量级服务、快速启动
工具开发CLI 工具、构建工具、脚手架
SSRReact/Vue 服务端渲染
BFFBackend For Frontend 层

不适合

场景原因
CPU 密集型复杂计算会阻塞事件循环
大量计算图像处理、视频编码、机器学习
内存密集型V8 内存限制(默认约 1.4GB)
CPU 密集型的解决方案
  • 使用 Worker Threads 多线程
  • 调用 C++ 插件
  • 拆分为微服务,用其他语言处理

Node.js vs 浏览器

特性Node.js浏览器
JavaScript 引擎V8V8/SpiderMonkey/JavaScriptCore
全局对象global / globalThiswindow / globalThis
DOM/BOM❌ 无✅ 有
文件系统fs 模块❌ 受限(File API)
网络✅ 完整(http/net)❌ 受限(fetch/XHR)
模块系统CommonJS + ESMESM
多线程Worker ThreadsWeb Workers
// 全局对象差异
// Node.js
console.log(global === globalThis); // true

// 浏览器
console.log(window === globalThis); // true

// 通用写法
console.log(globalThis); // 两边都可用

核心模块

// 内置模块,无需安装
import fs from 'fs'; // 文件系统
import path from 'path'; // 路径处理
import http from 'http'; // HTTP 服务
import https from 'https'; // HTTPS 服务
import url from 'url'; // URL 解析
import os from 'os'; // 操作系统信息
import crypto from 'crypto'; // 加密
import events from 'events'; // 事件
import stream from 'stream'; // 流
import util from 'util'; // 工具函数
import child_process from 'child_process'; // 子进程
import cluster from 'cluster'; // 集群
import worker_threads from 'worker_threads'; // 工作线程

全局变量

// 全局对象
console.log(global); // 全局命名空间
console.log(globalThis); // ES2020 标准

// 模块相关
console.log(__dirname); // 当前模块目录的绝对路径
console.log(__filename); // 当前模块文件的绝对路径
console.log(module); // 当前模块对象
console.log(exports); // 模块导出对象
console.log(require); // 模块引入函数

// 进程相关
console.log(process.env); // 环境变量
console.log(process.argv); // 命令行参数
console.log(process.cwd()); // 当前工作目录
console.log(process.pid); // 进程 ID
console.log(process.platform); // 操作系统平台
console.log(process.version); // Node.js 版本

// 定时器
setTimeout(() => {}, 1000);
setInterval(() => {}, 1000);
setImmediate(() => {});
process.nextTick(() => {});
ESM 中的差异

在 ES 模块中,__dirname__filename 不可用,需要这样获取:

import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

常见面试问题

Q1: Node.js 为什么是单线程的?有什么优缺点?

答案

Node.js 采用单线程模型主要是为了简化编程模型,避免多线程的复杂性(死锁、竞态条件等)。

优点

  • 简单:无需处理线程同步、锁等问题
  • 内存占用低:无需为每个连接创建线程
  • 高并发:事件驱动 + 非阻塞 I/O 处理大量连接
  • 适合 I/O 密集型:I/O 等待时可处理其他请求

缺点

  • CPU 密集型任务阻塞:长时间计算会阻塞事件循环
  • 无法利用多核 CPU:需要 cluster 或 worker_threads
  • 错误处理敏感:未捕获异常会导致进程崩溃
// CPU 密集型会阻塞
function fibonacci(n: number): number {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}

// ❌ 阻塞事件循环
app.get('/fib', (req, res) => {
const result = fibonacci(45); // 阻塞!
res.send({ result });
});

// ✅ 使用 Worker Thread
import { Worker } from 'worker_threads';

app.get('/fib', (req, res) => {
const worker = new Worker('./fib-worker.js', {
workerData: { n: 45 }
});
worker.on('message', (result) => res.send({ result }));
});

Q2: Node.js 如何处理高并发?

答案

Node.js 通过以下机制处理高并发:

  1. 事件循环:单线程轮询处理事件
  2. 非阻塞 I/O:I/O 操作异步执行,不阻塞主线程
  3. libuv 线程池:文件 I/O 等操作在后台线程执行
  4. 事件驱动:通过回调/Promise 处理异步结果
// 处理 1 万个并发连接
import { createServer } from 'http';

const server = createServer((req, res) => {
// 每个请求快速处理,不阻塞
res.writeHead(200);
res.end('OK');
});

server.listen(3000);

// 单线程可以轻松处理上万并发
// 因为大部分时间在等待 I/O,不是在计算

Q3: 什么是 libuv?它的作用是什么?

答案

libuv 是 Node.js 的底层库,提供:

功能说明
事件循环实现非阻塞 I/O
异步文件操作文件读写、监控
异步网络TCP/UDP Socket
线程池处理阻塞操作
信号处理进程信号
定时器setTimeout/setInterval
跨平台抽象统一 Windows/Unix API
// libuv 线程池默认 4 个线程
// 可通过环境变量调整
process.env.UV_THREADPOOL_SIZE = '8';

// 以下操作使用线程池:
// - fs 文件操作(除了 fs.watch)
// - crypto 加密操作
// - dns.lookup
// - 某些压缩操作

Q4: Node.js 的 process.nextTick 和 setImmediate 有什么区别?

答案

// process.nextTick:在当前操作完成后、事件循环继续前执行
// setImmediate:在事件循环的 check 阶段执行

setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
console.log('同步代码');

// 输出顺序:
// 同步代码
// nextTick
// setImmediate
特性process.nextTicksetImmediate
执行时机当前阶段结束后立即执行check 阶段执行
队列独立的 nextTick 队列事件循环队列
优先级更高较低
递归风险可能阻塞 I/O不会阻塞
注意

递归调用 process.nextTick 会阻塞事件循环,应优先使用 setImmediate

Q5: 如何在 Node.js 中处理 CPU 密集型任务?

答案

// 方案一:Worker Threads(推荐)
import { Worker, isMainThread, workerData, parentPort } from 'worker_threads';

if (isMainThread) {
const worker = new Worker(__filename, {
workerData: { n: 40 }
});
worker.on('message', (result) => {
console.log('Result:', result);
});
} else {
const { n } = workerData;
const result = fibonacci(n);
parentPort?.postMessage(result);
}

// 方案二:Child Process
import { fork } from 'child_process';

const child = fork('./heavy-computation.js');
child.send({ data: 'input' });
child.on('message', (result) => {
console.log('Result:', result);
});

// 方案三:Cluster 多进程
import cluster from 'cluster';
import { cpus } from 'os';

if (cluster.isPrimary) {
for (let i = 0; i < cpus().length; i++) {
cluster.fork();
}
} else {
// 工作进程
}

// 方案四:调用 C++ 插件(N-API/nan)
// 对于性能关键的计算,使用原生模块

相关链接