Skip to content

Node.js 2026 面试题汇总

聚焦 Node.js 18+ LTS 版本特性,覆盖事件循环、模块系统、Stream、进程管理、性能优化等核心主题。排除过时内容(如 callback hell、旧版 cluster 模式)。

目录


基础概念

1. Node.js 是什么?它的核心特性是什么?

答案要点

  • 定义:基于 Chrome V8 引擎的 JavaScript 运行时,用于构建高性能网络应用
  • 核心特性
    • 事件驱动:基于事件循环处理并发
    • 非阻塞 I/O:异步 I/O 操作,单线程高并发
    • 跨平台:支持 Windows、Linux、macOS
    • npm 生态:全球最大的开源库生态系统
javascript
// 非阻塞 I/O 示例
const fs = require('fs');

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

console.log('继续执行其他任务'); // 先输出这行

追问:Node.js 适合什么场景?不适合什么场景?


2. Node.js 的单线程是什么意思?它如何处理并发?

答案要点

  • 单线程:JavaScript 执行在单个主线程上,避免了多线程的锁和同步问题
  • 并发处理
    • 事件循环:主线程处理事件队列中的任务
    • libuv 线程池:I/O 操作(文件、DNS、加密)在 C++ 线程池中执行
    • Worker Threads:CPU 密集型任务可以使用 worker_threads 模块
javascript
// libuv 线程池处理 I/O
const crypto = require('crypto');

// 这个操作在线程池中执行,不阻塞主线程
crypto.pbkdf2('password', 'salt', 100000, 64, 'sha512', (err, key) => {
  console.log('密钥生成完成');
});

console.log('主线程继续执行'); // 先输出

追问:libuv 线程池默认大小是多少?如何调整?


3. Node.js 18+ LTS 有哪些重要新特性?

答案要点

Node.js 18 (2022 LTS)

  • Fetch API:原生支持 fetch(),无需 node-fetch
  • Test Runner:内置测试框架 node:test
  • Web Streams API:标准化的 Stream API

Node.js 20 (2023 LTS)

  • 权限模型--experimental-permission 限制文件/网络访问
  • 稳定的 Test Runnernode:test 成为稳定特性
  • 单可执行应用(SEA):打包成单个可执行文件

Node.js 22 (2024)

  • require() ESM:实验性支持 require() 导入 ESM 模块
  • WebSocket 客户端:原生 WebSocket 支持
javascript
// Node.js 18+ 原生 fetch
const response = await fetch('https://api.example.com/data');
const data = await response.json();

// Node.js 18+ 内置测试
import { test } from 'node:test';
import assert from 'node:assert';

test('加法测试', () => {
  assert.strictEqual(1 + 1, 2);
});

追问:Node.js 的 LTS 版本发布周期是怎样的?


4. global 对象和浏览器的 window 有什么区别?

答案要点

  • Node.js 全局对象global(Node.js 20+ 也支持 globalThis
  • 特有属性
    • process:进程信息和控制
    • Buffer:二进制数据处理
    • __dirname / __filename:当前文件路径(CommonJS)
    • require / module / exports:模块系统(CommonJS)
javascript
// Node.js 特有的全局变量
console.log(process.version);        // Node.js 版本
console.log(process.platform);       // 操作系统平台
console.log(__dirname);              // 当前目录路径
console.log(__filename);             // 当前文件路径

// globalThis 统一浏览器和 Node.js
globalThis.myVar = 'hello';
console.log(global.myVar); // "hello"

追问:ESM 模块中没有 __dirname,如何获取当前文件路径?


模块系统

5. CommonJS 和 ES Modules 的区别是什么?

答案要点

特性CommonJS (CJS)ES Modules (ESM)
语法require() / module.exportsimport / export
加载时机运行时同步加载编译时静态分析
导出值拷贝活绑定(引用)
顶层 await❌ 不支持✅ 支持
文件扩展名.js / .cjs.mjs / .js(需 "type": "module"
Tree-shaking❌ 不支持✅ 支持
javascript
// ========== CommonJS ==========
// math.cjs
let count = 0;
module.exports = {
  count,
  increment: () => ++count
};

// main.cjs
const { count, increment } = require('./math.cjs');
increment();
console.log(count); // 0(值拷贝,不会更新)

// ========== ES Modules ==========
// math.mjs
export let count = 0;
export function increment() { count++; }

// main.mjs
import { count, increment } from './math.mjs';
increment();
console.log(count); // 1(活绑定,会更新)

追问:如何在 Node.js 中混用 CJS 和 ESM?


6. package.jsontype 字段有什么作用?

答案要点

  • "type": "module".js 文件按 ESM 处理,CJS 需用 .cjs 扩展名
  • "type": "commonjs"(默认):.js 文件按 CJS 处理,ESM 需用 .mjs 扩展名
  • 不写 type 字段:等同于 "commonjs"
json
{
  "name": "my-app",
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  }
}

追问exports 字段和 main 字段的区别?


7. Node.js 的模块解析算法是怎样的?

答案要点

相对路径/绝对路径

  1. 精确匹配文件(带扩展名)
  2. 尝试添加 .js / .json / .node
  3. 尝试作为目录,查找 package.jsonmainindex.js

模块名(如 express

  1. 从当前目录的 node_modules 查找
  2. 逐级向上查找父目录的 node_modules
  3. 查找全局 node_modules
javascript
// 相对路径
require('./utils');          // 查找 utils.js / utils.json / utils/index.js

// 模块名
require('express');          // 查找 node_modules/express

// 内置模块(优先级最高)
require('fs');               // 直接返回内置 fs 模块

追问node_modules 的查找顺序是怎样的?


8. 如何实现一个简单的模块缓存机制?

答案要点

  • Node.js 会缓存已加载的模块,存储在 require.cache
  • 同一模块多次 require() 只执行一次
  • 可以通过 delete require.cache[modulePath] 清除缓存
javascript
// counter.js
let count = 0;
module.exports = {
  increment: () => ++count,
  getCount: () => count
};

// main.js
const counter1 = require('./counter');
const counter2 = require('./counter');

counter1.increment();
console.log(counter2.getCount()); // 1(共享同一实例)

// 清除缓存
delete require.cache[require.resolve('./counter')];
const counter3 = require('./counter');
console.log(counter3.getCount()); // 0(重新加载)

追问:ESM 的模块缓存机制和 CJS 有什么不同?


异步编程

9. Node.js 中有哪些异步编程方式?

答案要点

  1. Callback(旧,不推荐)
  2. Promise
  3. async/await(推荐)
  4. EventEmitter(事件驱动)
javascript
const fs = require('fs').promises;

// 1. Callback(旧方式)
require('fs').readFile('file.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// 2. Promise
fs.readFile('file.txt', 'utf8')
  .then(data => console.log(data))
  .catch(err => console.error(err));

// 3. async/await(推荐)
async function readFile() {
  try {
    const data = await fs.readFile('file.txt', 'utf8');
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

// 4. EventEmitter
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('data', (data) => console.log(data));
emitter.emit('data', 'hello');

追问:如何将 callback 风格的 API 转换为 Promise?


10. util.promisify 的作用是什么?

答案要点

  • 将 callback 风格的函数转换为返回 Promise 的函数
  • 要求 callback 遵循 (err, result) 的 Node.js 约定
javascript
const util = require('util');
const fs = require('fs');

// 转换为 Promise 版本
const readFile = util.promisify(fs.readFile);

// 使用 async/await
async function main() {
  const data = await readFile('file.txt', 'utf8');
  console.log(data);
}

// 自定义 promisify
function promisify(fn) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, result) => {
        if (err) reject(err);
        else resolve(result);
      });
    });
  };
}

追问:如果函数的 callback 不是 (err, result) 格式怎么办?


11. Promise.allPromise.allSettled 的区别?

答案要点

  • Promise.all:所有 Promise 成功才成功,任一失败立即失败
  • Promise.allSettled:等待所有 Promise 完成(无论成功失败),返回结果数组
javascript
const promises = [
  Promise.resolve(1),
  Promise.reject(new Error('失败')),
  Promise.resolve(3)
];

// Promise.all:遇到失败立即拒绝
try {
  await Promise.all(promises);
} catch (err) {
  console.log('有 Promise 失败了'); // 会执行
}

// Promise.allSettled:等待所有完成
const results = await Promise.allSettled(promises);
console.log(results);
// [
//   { status: 'fulfilled', value: 1 },
//   { status: 'rejected', reason: Error },
//   { status: 'fulfilled', value: 3 }
// ]

追问Promise.racePromise.any 的区别?


事件循环

12. Node.js 事件循环的六个阶段是什么?

答案要点

text
   ┌───────────────────────────┐
┌─>│           timers          │ 执行 setTimeout/setInterval 回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │ 执行延迟到下一轮的 I/O 回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │ 内部使用
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │ 执行 setImmediate 回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │ 执行 close 事件回调
   └───────────────────────────┘

关键阶段

  • timers:执行 setTimeout()setInterval() 的回调
  • poll:检索新的 I/O 事件,执行 I/O 回调(几乎所有回调,除了 close、timers、setImmediate)
  • check:执行 setImmediate() 回调

追问process.nextTick()setImmediate() 的执行顺序?


13. process.nextTick()setImmediate() 的区别?

答案要点

  • process.nextTick():在当前操作完成后、事件循环继续之前立即执行
  • setImmediate():在事件循环的 check 阶段执行
  • 执行顺序process.nextTick() > 微任务(Promise) > setImmediate()
javascript
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('Promise'));
console.log('同步代码');

// 输出顺序:
// 同步代码
// nextTick
// Promise
// setImmediate

注意:过度使用 process.nextTick() 可能导致事件循环饥饿(I/O 永远得不到执行)。

追问:什么情况下应该用 setImmediate() 而不是 process.nextTick()


14. 微任务和宏任务在 Node.js 中的执行顺序?

答案要点

  • 微任务(Microtask)Promise.then/catch/finallyprocess.nextTick()queueMicrotask()
  • 宏任务(Macrotask)setTimeoutsetIntervalsetImmediate、I/O 操作

执行规则

  1. 执行同步代码
  2. 清空 process.nextTick() 队列
  3. 清空微任务队列(Promise)
  4. 执行一个宏任务
  5. 重复步骤 2-4
javascript
setTimeout(() => console.log('timeout1'), 0);
setImmediate(() => console.log('immediate1'));

Promise.resolve().then(() => {
  console.log('promise1');
  process.nextTick(() => console.log('nextTick inside promise'));
});

process.nextTick(() => {
  console.log('nextTick1');
  Promise.resolve().then(() => console.log('promise inside nextTick'));
});

// 输出顺序:
// nextTick1
// promise inside nextTick
// promise1
// nextTick inside promise
// timeout1 或 immediate1(顺序不确定,取决于事件循环启动时机)

追问:为什么 setTimeout(fn, 0)setImmediate(fn) 的执行顺序不确定?


Stream 流

15. Node.js 中有哪几种 Stream?各自的应用场景?

答案要点

类型说明常见实例
Readable可读流fs.createReadStreamhttp.IncomingMessage
Writable可写流fs.createWriteStreamhttp.ServerResponse
Duplex双工流(可读可写)net.SocketTCP sockets
Transform转换流(读写过程中转换数据)zlib.createGzipcrypto.createCipher
javascript
const fs = require('fs');
const zlib = require('zlib');

// 读取文件 → 压缩 → 写入新文件
fs.createReadStream('input.txt')
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream('input.txt.gz'));

// 链式处理:Transform 流
const { Transform } = require('stream');

const upperCaseTransform = new Transform({
  transform(chunk, encoding, callback) {
    this.push(chunk.toString().toUpperCase());
    callback();
  }
});

process.stdin.pipe(upperCaseTransform).pipe(process.stdout);

追问:Stream 的背压(backpressure)机制是什么?


16. 什么是背压(Backpressure)?如何处理?

答案要点

  • 背压:当写入速度超过读取速度时,数据在内存中堆积,可能导致内存溢出
  • 处理机制
    • writable.write() 返回 false 时,停止写入
    • 监听 drain 事件,恢复写入
javascript
const fs = require('fs');

const readable = fs.createReadStream('large-file.txt');
const writable = fs.createWriteStream('output.txt');

readable.on('data', (chunk) => {
  const canContinue = writable.write(chunk);
  
  if (!canContinue) {
    console.log('背压,暂停读取');
    readable.pause();
  }
});

writable.on('drain', () => {
  console.log('缓冲区已清空,恢复读取');
  readable.resume();
});

// 使用 pipe 自动处理背压(推荐)
readable.pipe(writable);

追问pipe() 内部是如何处理背压的?


17. 如何实现一个自定义的 Transform Stream?

答案要点

  • 继承 stream.Transform
  • 实现 _transform(chunk, encoding, callback) 方法
  • 可选实现 _flush(callback) 方法(流结束时调用)
javascript
const { Transform } = require('stream');

class JSONParser extends Transform {
  constructor(options) {
    super(options);
    this._buffer = '';
  }

  _transform(chunk, encoding, callback) {
    this._buffer += chunk.toString();
    
    // 按行分割
    const lines = this._buffer.split('\n');
    this._buffer = lines.pop(); // 保留不完整的行
    
    for (const line of lines) {
      try {
        const obj = JSON.parse(line);
        this.push(JSON.stringify(obj, null, 2) + '\n');
      } catch (err) {
        // 忽略无效 JSON
      }
    }
    
    callback();
  }

  _flush(callback) {
    // 处理最后剩余的数据
    if (this._buffer) {
      try {
        const obj = JSON.parse(this._buffer);
        this.push(JSON.stringify(obj, null, 2) + '\n');
      } catch (err) {}
    }
    callback();
  }
}

// 使用
process.stdin
  .pipe(new JSONParser())
  .pipe(process.stdout);

追问_transform_flush 的区别?


文件系统

18. fs.readFilefs.createReadStream 的区别?

答案要点

特性fs.readFilefs.createReadStream
读取方式一次性读取整个文件到内存分块读取(Stream)
内存占用高(文件大小)低(固定缓冲区,默认 64KB)
适用场景小文件(< 10MB)大文件、实时处理
性能小文件快大文件快,内存友好
javascript
const fs = require('fs');

// readFile:适合小文件
fs.readFile('small.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data); // 完整内容
});

// createReadStream:适合大文件
const stream = fs.createReadStream('large.txt', { encoding: 'utf8' });
stream.on('data', (chunk) => {
  console.log('收到数据块:', chunk.length);
});
stream.on('end', () => {
  console.log('读取完成');
});

// Promise 版本(Node.js 10+)
const fsPromises = require('fs').promises;
const data = await fsPromises.readFile('file.txt', 'utf8');

追问:如何读取超大文件(如 10GB)而不占用过多内存?


19. fs.watchfs.watchFile 的区别?

答案要点

  • fs.watch:基于操作系统的文件系统事件(inotify、FSEvents),性能高
  • fs.watchFile:轮询文件状态(stat),性能低但兼容性好
javascript
const fs = require('fs');

// fs.watch(推荐)
fs.watch('file.txt', (eventType, filename) => {
  console.log(`事件类型: ${eventType}`); // 'rename' 或 'change'
  console.log(`文件名: ${filename}`);
});

// fs.watchFile(轮询,不推荐)
fs.watchFile('file.txt', { interval: 1000 }, (curr, prev) => {
  console.log(`当前修改时间: ${curr.mtime}`);
  console.log(`之前修改时间: ${prev.mtime}`);
});

// 第三方库 chokidar(生产推荐)
const chokidar = require('chokidar');
chokidar.watch('*.js').on('change', (path) => {
  console.log(`${path} 已修改`);
});

追问fs.watch 在不同操作系统上的行为差异?


20. 如何安全地写入文件,避免数据丢失?

答案要点

  • 原子写入:先写入临时文件,再重命名覆盖原文件
  • 使用 fsync:确保数据刷新到磁盘
  • 错误处理:捕获所有可能的错误
javascript
const fs = require('fs').promises;
const path = require('path');

async function safeWriteFile(filePath, data) {
  const tempPath = `${filePath}.tmp`;
  
  try {
    // 1. 写入临时文件
    await fs.writeFile(tempPath, data, 'utf8');
    
    // 2. 确保数据刷新到磁盘(可选,性能影响大)
    const fd = await fs.open(tempPath, 'r+');
    await fd.sync();
    await fd.close();
    
    // 3. 原子重命名
    await fs.rename(tempPath, filePath);
    
    console.log('文件写入成功');
  } catch (err) {
    // 清理临时文件
    try {
      await fs.unlink(tempPath);
    } catch {}
    throw err;
  }
}

// 使用
await safeWriteFile('config.json', JSON.stringify({ key: 'value' }));

追问:为什么 rename 是原子操作?


HTTP 与网络

21. 如何创建一个简单的 HTTP 服务器?

答案要点

javascript
const http = require('http');

const server = http.createServer((req, res) => {
  // 设置响应头
  res.writeHead(200, { 'Content-Type': 'application/json' });
  
  // 路由处理
  if (req.url === '/api/users' && req.method === 'GET') {
    res.end(JSON.stringify({ users: ['Alice', 'Bob'] }));
  } else if (req.url === '/api/users' && req.method === 'POST') {
    let body = '';
    req.on('data', chunk => { body += chunk; });
    req.on('end', () => {
      const user = JSON.parse(body);
      res.end(JSON.stringify({ success: true, user }));
    });
  } else {
    res.writeHead(404);
    res.end('Not Found');
  }
});

server.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

追问http 模块和 express 的区别?


22. Node.js 18+ 的原生 fetch API 和浏览器的有什么区别?

答案要点

  • Node.js 18+ 内置 fetch,基于 undici 实现
  • 主要区别
    • Node.js 不支持 CORS(服务端没有同源策略)
    • 默认不发送 cookies(需要手动设置)
    • 支持 file:// 协议
javascript
// Node.js 18+ 原生 fetch
const response = await fetch('https://api.example.com/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ key: 'value' })
});

const data = await response.json();

// 错误处理
if (!response.ok) {
  throw new Error(`HTTP error! status: ${response.status}`);
}

// 超时控制
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);

const response2 = await fetch('https://slow-api.com', {
  signal: controller.signal
});

追问:如何在 Node.js 中实现请求重试?


23. 如何处理大文件上传?

答案要点

  • 使用 Stream 处理,避免一次性加载到内存
  • 使用 multipart/form-data 解析(如 busboymulter
  • 分块上传(Chunked Upload)
javascript
const http = require('http');
const fs = require('fs');
const path = require('path');
const Busboy = require('busboy');

http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/upload') {
    const busboy = Busboy({ headers: req.headers });
    
    busboy.on('file', (fieldname, file, info) => {
      const { filename } = info;
      const savePath = path.join(__dirname, 'uploads', filename);
      
      // 直接 pipe 到文件,不占用内存
      file.pipe(fs.createWriteStream(savePath));
      
      file.on('end', () => {
        console.log(`文件 ${filename} 上传完成`);
      });
    });
    
    busboy.on('finish', () => {
      res.writeHead(200);
      res.end('Upload complete');
    });
    
    req.pipe(busboy);
  } else {
    res.writeHead(404);
    res.end();
  }
}).listen(3000);

追问:如何实现断点续传?


24. WebSocket 在 Node.js 中如何实现?

答案要点

  • 使用 ws 库(最流行)或 Node.js 22+ 原生 WebSocket
  • 双向实时通信,适合聊天、实时推送
javascript
const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  console.log('客户端已连接');
  
  // 接收消息
  ws.on('message', (data) => {
    console.log('收到消息:', data.toString());
    
    // 广播给所有客户端
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(data);
      }
    });
  });
  
  // 发送消息
  ws.send('欢迎连接!');
  
  // 心跳检测
  const interval = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.ping();
    }
  }, 30000);
  
  ws.on('close', () => {
    console.log('客户端已断开');
    clearInterval(interval);
  });
});

追问:WebSocket 和 HTTP 长轮询的区别?


进程与集群

25. child_process 模块有哪些方法?各自的应用场景?

答案要点

方法说明应用场景
exec()执行 shell 命令,缓冲输出简单命令,输出较小
execFile()直接执行文件,不启动 shell性能更好,安全性更高
spawn()启动子进程,Stream 输出长时间运行、大量输出
fork()专门用于运行 Node.js 脚本多进程计算、IPC 通信
javascript
const { exec, spawn, fork } = require('child_process');

// exec:适合简单命令
exec('ls -la', (err, stdout, stderr) => {
  if (err) throw err;
  console.log(stdout);
});

// spawn:适合长时间运行
const child = spawn('find', ['.', '-type', 'f']);
child.stdout.on('data', (data) => {
  console.log(`输出: ${data}`);
});

// fork:Node.js 进程间通信
const worker = fork('./worker.js');
worker.send({ task: 'compute', data: [1, 2, 3] });
worker.on('message', (result) => {
  console.log('计算结果:', result);
});

追问execspawn 的内存占用差异?


26. 如何使用 cluster 模块实现多进程?

答案要点

  • Master-Worker 模式:主进程负责管理,工作进程处理请求
  • 负载均衡:默认使用轮询(round-robin)策略
  • 进程崩溃重启:监听 exit 事件自动重启
javascript
const cluster = require('cluster');
const http = require('http');
const os = require('os');

if (cluster.isMaster) {
  const numCPUs = os.cpus().length;
  console.log(`主进程 ${process.pid} 正在运行`);
  
  // 创建工作进程
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  
  // 工作进程崩溃时重启
  cluster.on('exit', (worker, code, signal) => {
    console.log(`工作进程 ${worker.process.pid} 已退出`);
    cluster.fork(); // 重启
  });
} else {
  // 工作进程创建 HTTP 服务器
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(`由进程 ${process.pid} 处理\n`);
  }).listen(8000);
  
  console.log(`工作进程 ${process.pid} 已启动`);
}

追问clusterworker_threads 的区别?


27. worker_threads 适合什么场景?

答案要点

  • CPU 密集型任务:图像处理、加密、大数据计算
  • 共享内存:通过 SharedArrayBuffer 共享数据
  • 不适合 I/O 密集型:I/O 操作已经是异步的,用 worker 反而增加开销
javascript
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

if (isMainThread) {
  // 主线程
  const worker = new Worker(__filename, {
    workerData: { num: 1000000 }
  });
  
  worker.on('message', (result) => {
    console.log('计算结果:', result);
  });
  
  worker.on('error', (err) => {
    console.error('Worker 错误:', err);
  });
} else {
  // Worker 线程
  const { num } = workerData;
  
  // CPU 密集型计算
  function isPrime(n) {
    for (let i = 2; i <= Math.sqrt(n); i++) {
      if (n % i === 0) return false;
    }
    return n > 1;
  }
  
  const primes = [];
  for (let i = 2; i <= num; i++) {
    if (isPrime(i)) primes.push(i);
  }
  
  parentPort.postMessage(primes.length);
}

追问:如何在 Worker 之间共享数据?


性能优化

28. Node.js 性能优化有哪些常见手段?

答案要点

代码层面

  • 使用 Stream 处理大数据
  • 避免同步阻塞操作(fs.readFileSync
  • 使用 Buffer 而非字符串拼接
  • 缓存计算结果(内存缓存、Redis)

架构层面

  • 使用 cluster 或 PM2 多进程
  • 反向代理(Nginx)
  • 负载均衡
  • CDN 静态资源

监控层面

  • 使用 clinic.js 诊断性能瓶颈
  • --inspect 开启 Chrome DevTools 调试
  • APM 工具(New Relic、Datadog)
javascript
// 错误示例:同步阻塞
const data = fs.readFileSync('large-file.txt'); // ❌ 阻塞事件循环

// 正确示例:异步 + Stream
fs.createReadStream('large-file.txt')
  .on('data', chunk => process(chunk))
  .on('end', () => console.log('完成'));

// 缓存示例
const cache = new Map();

async function getUser(id) {
  if (cache.has(id)) {
    return cache.get(id);
  }
  
  const user = await db.findUser(id);
  cache.set(id, user);
  return user;
}

追问:如何诊断内存泄漏?


29. 如何监控 Node.js 应用的内存使用?

答案要点

  • process.memoryUsage():查看当前内存使用情况
  • 堆快照(Heap Snapshot):使用 Chrome DevTools 分析
  • --max-old-space-size:调整 V8 堆内存上限
javascript
// 查看内存使用
const used = process.memoryUsage();
console.log({
  rss: `${Math.round(used.rss / 1024 / 1024)}MB`,           // 常驻内存
  heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`, // 堆总大小
  heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,   // 堆已使用
  external: `${Math.round(used.external / 1024 / 1024)}MB`    // C++ 对象
});

// 定期监控
setInterval(() => {
  const usage = process.memoryUsage();
  if (usage.heapUsed > 500 * 1024 * 1024) { // 超过 500MB
    console.warn('内存使用过高!');
  }
}, 60000);

// 生成堆快照
const v8 = require('v8');
const fs = require('fs');

const snapshot = v8.writeHeapSnapshot();
console.log('堆快照已保存:', snapshot);

追问:常见的内存泄漏场景有哪些?


30. 什么是事件循环阻塞?如何避免?

答案要点

  • 阻塞:同步操作或长时间 CPU 计算占用事件循环,导致其他任务无法执行
  • 检测blocked-at 库、--perf-basic-prof 性能分析
  • 避免方法
    • 拆分大任务为小任务
    • 使用 setImmediate() 让出事件循环
    • CPU 密集型任务用 Worker Threads
javascript
// ❌ 阻塞事件循环
function heavyCompute() {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i;
  }
  return sum;
}

// ✅ 拆分任务
function heavyComputeAsync(max, callback) {
  let sum = 0;
  let i = 0;
  
  function chunk() {
    const end = Math.min(i + 1e6, max);
    for (; i < end; i++) {
      sum += i;
    }
    
    if (i < max) {
      setImmediate(chunk); // 让出事件循环
    } else {
      callback(sum);
    }
  }
  
  chunk();
}

heavyComputeAsync(1e9, (result) => {
  console.log('计算完成:', result);
});

追问:如何测量事件循环延迟?


安全

31. Node.js 应用常见的安全问题有哪些?

答案要点

  1. 依赖漏洞:使用 npm audit 检查
  2. 注入攻击:SQL 注入、命令注入、XSS
  3. 敏感信息泄露:环境变量、日志
  4. 拒绝服务(DoS):正则表达式 ReDoS、大文件上传
  5. 不安全的反序列化JSON.parse 原型污染
javascript
// ❌ SQL 注入风险
const userId = req.query.id;
db.query(`SELECT * FROM users WHERE id = ${userId}`); // 危险!

// ✅ 使用参数化查询
db.query('SELECT * FROM users WHERE id = ?', [userId]);

// ❌ 命令注入风险
const { exec } = require('child_process');
exec(`ping ${req.query.host}`); // 危险!

// ✅ 使用白名单验证
const allowedHosts = ['google.com', 'example.com'];
if (allowedHosts.includes(req.query.host)) {
  exec(`ping ${req.query.host}`);
}

// 环境变量管理
require('dotenv').config();
const apiKey = process.env.API_KEY; // 不要硬编码密钥

追问:如何防止原型污染攻击?


32. 如何安全地处理用户输入?

答案要点

  • 验证:使用 Joi、Zod 等库验证输入
  • 转义:防止 XSS(使用 escape-html
  • 限制大小:防止 DoS
  • HTTPS:加密传输
javascript
const Joi = require('joi');

// 输入验证
const userSchema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),
  email: Joi.string().email().required(),
  age: Joi.number().integer().min(0).max(150)
});

app.post('/register', (req, res) => {
  const { error, value } = userSchema.validate(req.body);
  
  if (error) {
    return res.status(400).json({ error: error.details[0].message });
  }
  
  // 使用验证后的数据
  createUser(value);
});

// 防止 XSS
const escapeHtml = require('escape-html');
const userInput = req.body.comment;
const safe = escapeHtml(userInput);

// 限制请求大小
app.use(express.json({ limit: '1mb' }));

追问:什么是 CSRF 攻击?如何防御?