NodeJS-重新学习-20260407

7 阅读11分钟

NodeJS

NodeJS 基础

NodeJS 作用、特点与应用场景

包含的题目:

  1. NodeJS 的作用是什么?
  2. NodeJS 的特点是什么?
  3. NodeJS 与浏览器的区别是什么?(高频考点)
  4. NodeJS 的应用场景有哪些?

详细答案:

NodeJS 作用:基于 Chrome V8 引擎的 JavaScript 运行时环境,用于构建高性能、可扩展的网络应用。

NodeJS 特点

  1. 非阻塞 I/O:异步处理 I/O 操作,高并发处理能力
  2. 事件驱动:基于事件循环的编程模型
  3. 单线程:主线程单线程,通过事件循环处理并发
  4. 跨平台:支持 Windows、Linux、macOS
  5. NPM 生态:丰富的包管理器和模块库
  6. 高性能:V8 引擎编译执行,速度快

NodeJS vs 浏览器

方面NodeJS浏览器
运行环境服务器端客户端
全局对象globalwindow
文件系统有完整访问权限受限(沙盒)
DOM 操作不支持支持
模块系统CommonJS/ES ModulesES Modules
I/O 操作非阻塞,异步有限制
应用场景服务器、工具链网页、Web 应用

应用场景

  • Web 服务器:Express、Koa、Fastify
  • API 网关:微服务架构中的 API 聚合
  • 实时应用:聊天、游戏、协作工具
  • 工具开发:构建工具、CLI 工具
  • 微服务:轻量级服务节点
  • 中间层:BFF(Backend for Frontend)
  • IoT 应用:物联网设备控制

NodeJS 模块系统

CommonJS 与 ES Modules

包含的题目: 5. 什么是 CommonJS?(高频考点) 6. require 函数的使用 7. module.exports 与 exports 的区别(高频考点) 8. 模块的加载机制是什么? 9. 模块的缓存机制是什么? 10. 如何解决模块的循环依赖? 11. CommonJS 与 ES Modules 的区别(高频考点)

详细答案:

CommonJS 模块

// 导出模块
// math.js
module.exports = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
};

// 或
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;

// 导入模块
// app.js
const math = require('./math.js');
console.log(math.add(1, 2));

module.exports vs exports

  • module.exports:实际的导出对象
  • exportsmodule.exports 的引用
  • 重要:直接赋值给 exports 无效,必须赋值给 module.exportsexports 的属性

模块加载机制

  1. 路径分析:解析模块路径
  2. 文件定位:查找 .js、.json、.node 文件
  3. 编译执行:加载并执行模块代码
  4. 加入缓存:缓存模块,避免重复加载

缓存机制:每个模块第一次加载后会被缓存,后续 require 返回缓存结果。

循环依赖解决

// a.js
console.log('a 开始');
exports.done = false;
const b = require('./b.js');
console.log('在 a 中,b.done = %j', b.done);
exports.done = true;
console.log('a 结束');

// b.js  
console.log('b 开始');
exports.done = false;
const a = require('./a.js');
console.log('在 b 中,a.done = %j', a.done);
exports.done = true;
console.log('b 结束');

CommonJS vs ES Modules

特性CommonJSES Modules
语法require/module.exportsimport/export
加载运行时加载,同步编译时加载,异步
值类型值的拷贝(基本类型)值的引用
动态性支持动态 require静态 import
树摇优化不支持支持
循环依赖支持,但有风险支持,更安全

ES Modules 示例

// math.mjs
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// app.mjs  
import { add, subtract } from './math.mjs';
console.log(add(1, 2));

NodeJS 事件循环

事件循环原理与阶段

包含的题目: 12. 什么是事件循环?(高频考点) 13. 事件循环的原理是什么? 14. 事件循环的阶段有哪些?(高频考点) 15. timers 阶段的作用 16. poll 阶段的作用 17. check 阶段的作用 18. 什么是宏任务? 19. 什么是微任务? 20. setImmediate 的使用 21. process.nextTick 的使用 22. Node.js 与浏览器事件循环的区别(高频考点)

详细答案:

事件循环阶段

  1. timers:执行 setTimeout 和 setInterval 回调
  2. pending callbacks:执行系统操作回调(如 TCP 错误)
  3. idle, prepare:内部使用
  4. poll:检索新的 I/O 事件,执行相关回调
  5. check:执行 setImmediate 回调
  6. close callbacks:执行关闭事件的回调(如 socket.on('close'))

事件循环流程

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤           poll            │
   └─────────────┬─────────────┘
   ┌─────────────┴─────────────┐
   │           check           │
   └─────────────┬─────────────┘
   ┌─────────────┴─────────────┐
   │      close callbacks      │
   └───────────────────────────┘

宏任务 vs 微任务

  • 宏任务:setTimeout、setInterval、setImmediate、I/O 操作
  • 微任务:process.nextTick、Promise.then/catch/finally、queueMicrotask

执行顺序

console.log('1');

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

Promise.resolve().then(() => console.log('3'));

process.nextTick(() => console.log('4'));

setImmediate(() => console.log('5'));

console.log('6');

// 输出顺序:1 6 4 3 2 5
// nextTick > Promise > setTimeout > setImmediate

setImmediate vs setTimeout

  • setImmediate:在 check 阶段执行
  • setTimeout:在 timers 阶段执行(最小延迟 1ms)

process.nextTick

  • 在当前操作完成后,事件循环继续之前执行
  • 不属于事件循环的阶段
  • 可能造成递归调用,导致事件循环饥饿

Node.js vs 浏览器事件循环

环境阶段微任务队列特点
Node.js6个阶段nextTick 队列、Promise 队列更复杂,支持更多 I/O
浏览器渲染前/后单个微任务队列与渲染相关,支持 requestAnimationFrame

NodeJS 异步编程

回调、Promise、async/await

包含的题目: 23. 回调函数的使用 24. 如何解决回调地狱? 25. Promise 的使用 26. async/await 的使用 27. EventEmitter 的使用 28. Stream 的使用 29. Stream 的类型有哪些?

详细答案:

回调地狱

// 回调地狱示例
fs.readFile('file1.txt', (err, data1) => {
  if (err) throw err;
  fs.readFile('file2.txt', (err, data2) => {
    if (err) throw err;
    fs.writeFile('result.txt', data1 + data2, (err) => {
      if (err) throw err;
      console.log('完成');
    });
  });
});

Promise 解决回调地狱

const fs = require('fs').promises;

fs.readFile('file1.txt')
  .then(data1 => fs.readFile('file2.txt'))
  .then(data2 => fs.writeFile('result.txt', data1 + data2))
  .then(() => console.log('完成'))
  .catch(err => console.error(err));

async/await

async function processFiles() {
  try {
    const data1 = await fs.readFile('file1.txt');
    const data2 = await fs.readFile('file2.txt');
    await fs.writeFile('result.txt', data1 + data2);
    console.log('完成');
  } catch (err) {
    console.error(err);
  }
}

EventEmitter 事件发射器

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

// 监听事件
myEmitter.on('event', (arg1, arg2) => {
  console.log('事件触发', arg1, arg2);
});

// 触发事件
myEmitter.emit('event', '参数1', '参数2');

// 一次性监听
myEmitter.once('once', () => {
  console.log('只触发一次');
});

// 错误处理
myEmitter.on('error', (err) => {
  console.error('错误:', err);
});

Stream 流处理

const fs = require('fs');

// 可读流
const readable = fs.createReadStream('input.txt');
// 可写流  
const writable = fs.createWriteStream('output.txt');

// 管道连接
readable.pipe(writable);

// 流事件
readable.on('data', (chunk) => {
  console.log('收到数据:', chunk.length);
});

readable.on('end', () => {
  console.log('读取完成');
});

writable.on('finish', () => {
  console.log('写入完成');
});

Stream 类型

  1. Readable:可读流(文件读取、HTTP 请求)
  2. Writable:可写流(文件写入、HTTP 响应)
  3. Duplex:双向流(TCP Socket)
  4. Transform:转换流(压缩、加密)

NodeJS 文件系统

fs 模块操作

包含的题目: 30. NodeJS 的文件系统模块 31. fs 模块的使用 32. 文件的读取方法 33. 文件的写入方法 34. 文件的删除方法 35. 文件的监听方法 36. 同步与异步文件操作的区别

详细答案:

文件读取

const fs = require('fs');

// 异步读取
fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// Promise 方式(Node.js 14+)
const fs = require('fs').promises;
fs.readFile('file.txt', 'utf8')
  .then(data => console.log(data))
  .catch(err => console.error(err));

// 同步读取(阻塞)
try {
  const data = fs.readFileSync('file.txt', 'utf8');
  console.log(data);
} catch (err) {
  console.error(err);
}

文件写入

// 异步写入
fs.writeFile('file.txt', 'Hello Node.js', (err) => {
  if (err) throw err;
  console.log('写入完成');
});

// 追加写入
fs.appendFile('file.txt', '\n追加内容', (err) => {
  if (err) throw err;
  console.log('追加完成');
});

// 流式写入
const writeStream = fs.createWriteStream('file.txt');
writeStream.write('第一行\n');
writeStream.write('第二行\n');
writeStream.end();

文件操作

// 检查文件是否存在
fs.access('file.txt', fs.constants.F_OK, (err) => {
  console.log(err ? '不存在' : '存在');
});

// 删除文件
fs.unlink('file.txt', (err) => {
  if (err) throw err;
  console.log('删除完成');
});

// 重命名/移动文件
fs.rename('old.txt', 'new.txt', (err) => {
  if (err) throw err;
  console.log('重命名完成');
});

// 复制文件
fs.copyFile('source.txt', 'dest.txt', (err) => {
  if (err) throw err;
  console.log('复制完成');
});

文件监听

// 监听文件变化
fs.watch('file.txt', (eventType, filename) => {
  console.log(`事件类型: ${eventType}`);
  if (filename) {
    console.log(`文件名: ${filename}`);
  }
});

// 更稳定的监听(推荐)
const chokidar = require('chokidar');
const watcher = chokidar.watch('file.txt', {
  persistent: true,
  ignoreInitial: false,
});

watcher.on('change', (path) => {
  console.log(`文件变化: ${path}`);
});

目录操作

// 创建目录
fs.mkdir('newdir', { recursive: true }, (err) => {
  if (err) throw err;
  console.log('目录创建完成');
});

// 读取目录
fs.readdir('.', (err, files) => {
  if (err) throw err;
  console.log('目录内容:', files);
});

// 删除目录
fs.rmdir('dir', { recursive: true }, (err) => {
  if (err) throw err;
  console.log('目录删除完成');
});

NodeJS 网络编程

HTTP、TCP、WebSocket 服务器

包含的题目: 37. HTTP 模块的使用 38. 如何创建 HTTP 服务器? 39. HTTP 请求的处理 40. HTTP 响应的处理 41. TCP 模块的使用 42. 如何创建 TCP 服务器? 43. WebSocket 的使用

详细答案:

HTTP 服务器

const http = require('http');

const server = http.createServer((req, res) => {
  // 请求信息
  console.log('方法:', req.method);
  console.log('URL:', req.url);
  console.log('头部:', req.headers);
  
  // 设置响应头
  res.setHeader('Content-Type', 'application/json');
  res.setHeader('X-Powered-By', 'Node.js');
  
  // 根据不同路由处理
  if (req.url === '/') {
    res.statusCode = 200;
    res.end(JSON.stringify({ message: '首页' }));
  } else if (req.url === '/api/data') {
    res.statusCode = 200;
    res.end(JSON.stringify({ data: 'API 数据' }));
  } else {
    res.statusCode = 404;
    res.end(JSON.stringify({ error: '未找到' }));
  }
});

// 监听端口
server.listen(3000, () => {
  console.log('服务器运行在 http://localhost:3000');
});

// 请求体处理
let body = [];
req.on('data', (chunk) => {
  body.push(chunk);
}).on('end', () => {
  body = Buffer.concat(body).toString();
  console.log('请求体:', body);
});

HTTPS 服务器

const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('key.pem'),
  cert: fs.readFileSync('cert.pem'),
};

https.createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('HTTPS 服务器');
}).listen(443);

TCP 服务器

const net = require('net');

const server = net.createServer((socket) => {
  console.log('客户端连接:', socket.remoteAddress);
  
  // 接收数据
  socket.on('data', (data) => {
    console.log('收到数据:', data.toString());
    socket.write('服务器回应: ' + data);
  });
  
  // 连接关闭
  socket.on('end', () => {
    console.log('客户端断开');
  });
  
  // 错误处理
  socket.on('error', (err) => {
    console.error('Socket 错误:', err);
  });
});

// 监听端口
server.listen(8080, () => {
  console.log('TCP 服务器运行在端口 8080');
});

WebSocket 服务器

const WebSocket = require('ws');

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

wss.on('connection', (ws) => {
  console.log('新客户端连接');
  
  // 接收消息
  ws.on('message', (message) => {
    console.log('收到消息:', message);
    
    // 广播给所有客户端
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(`广播: ${message}`);
      }
    });
  });
  
  // 发送欢迎消息
  ws.send('欢迎连接 WebSocket 服务器');
  
  // 连接关闭
  ws.on('close', () => {
    console.log('客户端断开');
  });
});

NodeJS 进程与线程

进程管理、Worker Threads

包含的题目: 44. NodeJS 的进程 45. process 对象的使用 46. 环境变量的获取 47. 命令行参数的获取 48. Worker Threads 的使用 49. child_process 模块的使用 50. exec、spawn、fork 方法的区别(高频考点) 51. 进程间通信的方法

详细答案:

process 对象

// 环境变量
console.log('NODE_ENV:', process.env.NODE_ENV);
console.log('PATH:', process.env.PATH);

// 命令行参数
console.log('参数:', process.argv);
// node app.js arg1 arg2
// process.argv[0]: node 路径
// process.argv[1]: 脚本路径  
// process.argv[2]: 'arg1'

// 进程信息
console.log('进程ID:', process.pid);
console.log('工作目录:', process.cwd());
console.log('平台:', process.platform);
console.log('内存使用:', process.memoryUsage());

// 退出进程
process.exit(0); // 正常退出
process.exit(1); // 错误退出

// 信号处理
process.on('SIGINT', () => {
  console.log('收到 SIGINT 信号');
  process.exit(0);
});

子进程创建方法对比

方法特点适用场景
exec创建 shell,缓存输出简单命令,输出较小
spawn流式输入输出,无 shell大输出,实时处理
fork创建 Node.js 子进程,IPC计算密集型任务
execFile执行可执行文件执行外部程序

exec 示例

const { exec } = require('child_process');

exec('ls -la', (error, stdout, stderr) => {
  if (error) {
    console.error(`执行错误: ${error}`);
    return;
  }
  console.log(`输出: ${stdout}`);
  if (stderr) console.error(`错误: ${stderr}`);
});

spawn 示例

const { spawn } = require('child_process');

const ls = spawn('ls', ['-la']);

ls.stdout.on('data', (data) => {
  console.log(`输出: ${data}`);
});

ls.stderr.on('data', (data) => {
  console.error(`错误: ${data}`);
});

ls.on('close', (code) => {
  console.log(`进程退出,代码: ${code}`);
});

fork 示例

// parent.js
const { fork } = require('child_process');
const child = fork('./child.js');

child.send({ message: 'Hello child' });

child.on('message', (msg) => {
  console.log('来自子进程:', msg);
});

child.on('exit', (code) => {
  console.log('子进程退出:', code);
});

// child.js
process.on('message', (msg) => {
  console.log('来自父进程:', msg);
  process.send({ message: 'Hello parent' });
});

Worker Threads

const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
  // 主线程
  const worker = new Worker(__filename);
  
  worker.on('message', (msg) => {
    console.log('来自 Worker:', msg);
  });
  
  worker.postMessage('Hello Worker');
} else {
  // Worker 线程
  parentPort.on('message', (msg) => {
    console.log('来自主线程:', msg);
    parentPort.postMessage('Hello Main');
    
    // 执行 CPU 密集型任务
    const result = heavyCalculation();
    parentPort.postMessage({ result });
  });
}

进程间通信(IPC)

  1. 管道(pipe):父子进程通信
  2. 消息队列:系统级消息传递
  3. 共享内存:多个进程访问同一内存区域
  4. Socket:网络套接字通信
  5. 文件:通过文件系统通信

NodeJS 缓冲区

Buffer 操作

包含的题目: 52. 什么是 Buffer? 53. 如何创建 Buffer? 54. Buffer 的操作方法

详细答案:

Buffer 作用:处理二进制数据,如图片、文件、网络流等。

创建 Buffer

// 方式1:指定大小
const buf1 = Buffer.alloc(10); // 10字节,填充 0
const buf2 = Buffer.allocUnsafe(10); // 10字节,不初始化

// 方式2:从数组
const buf3 = Buffer.from([1, 2, 3, 4]);

// 方式3:从字符串
const buf4 = Buffer.from('Hello', 'utf8');

// 方式4:从已有 Buffer
const buf5 = Buffer.from(buf4);

Buffer 操作

const buf = Buffer.from('Hello World');

// 长度
console.log('长度:', buf.length);

// 读取
console.log('第1字节:', buf[0]); // 72 (H)
console.log('字符串:', buf.toString('utf8'));

// 写入
buf.write('Node', 0, 4, 'utf8');
console.log('修改后:', buf.toString());

// 复制
const buf2 = Buffer.alloc(11);
buf.copy(buf2);
console.log('复制:', buf2.toString());

// 比较
const buf3 = Buffer.from('Hello');
console.log('比较:', buf.compare(buf3));

// 切片
const slice = buf.slice(0, 5);
console.log('切片:', slice.toString());

// 连接
const buf4 = Buffer.from(' ');
const buf5 = Buffer.from('World');
const concat = Buffer.concat([buf, buf4, buf5]);
console.log('连接:', concat.toString());

// 填充
const buf6 = Buffer.alloc(10);
buf6.fill('A');
console.log('填充:', buf6.toString());

// 查找
console.log('查找 o:', buf.indexOf('o'));
console.log('查找 World:', buf.indexOf('World'));

// 遍历
for (const byte of buf) {
  console.log('字节:', byte);
}

// 转 JSON
const json = buf.toJSON();
console.log('JSON:', json);

NodeJS 包管理

npm、yarn、pnpm

包含的题目: 55. npm 的使用 56. 常用的 npm 命令 57. package.json 的配置 58. dependencies 与 devDependencies 的区别(高频考点) 59. npm、yarn、pnpm 的区别(高频考点)

详细答案:

package.json 示例

{
  "name": "my-app",
  "version": "1.0.0",
  "description": "Node.js 应用",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    "test": "jest",
    "build": "webpack",
    "lint": "eslint ."
  },
  "dependencies": {
    "express": "^4.18.0",
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "nodemon": "^2.0.20",
    "jest": "^29.0.0",
    "eslint": "^8.0.0"
  },
  "peerDependencies": {
    "react": ">=16.8.0"
  },
  "engines": {
    "node": ">=14.0.0",
    "npm": ">=6.0.0"
  },
  "keywords": ["node", "express", "api"],
  "author": "Your Name",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/username/repo.git"
  }
}

依赖类型

  • dependencies:生产环境依赖
  • devDependencies:开发环境依赖
  • peerDependencies:对等依赖,插件需要的主包版本
  • optionalDependencies:可选依赖,安装失败不阻塞
  • bundledDependencies:打包依赖,发布时包含

常用 npm 命令

# 初始化项目
npm init
npm init -y

# 安装依赖
npm install express
npm install express --save
npm install eslint --save-dev
npm install  # 安装所有依赖

# 全局安装
npm install -g nodemon

# 更新依赖
npm update
npm update express

# 卸载依赖
npm uninstall express
npm uninstall eslint --save-dev

# 查看信息
npm list
npm list --depth=0
npm view express version
npm outdated

# 运行脚本
npm run start
npm run dev
npm test

# 发布包
npm login
npm publish
npm version patch/minor/major

包管理工具对比

工具特点优势
npmNode.js 官方包管理器生态最全,兼容性好
yarnFacebook 开发速度快,确定性安装
pnpm硬链接+符号链接节省磁盘空间,安装快

yarn 命令

yarn add express
yarn add eslint --dev
yarn remove express
yarn upgrade
yarn why express  # 查看依赖原因

pnpm 命令

pnpm add express
pnpm add eslint -D
pnpm remove express
pnpm update

NodeJS 性能优化

性能优化方法与工具

包含的题目: 60. NodeJS 性能优化的方法有哪些? 61. 如何检测内存泄漏? 62. 如何解决内存泄漏? 63. 集群模式的使用 64. 负载均衡的实现

详细答案:

性能优化方法

  1. 代码优化:避免同步阻塞操作,使用异步
  2. 内存管理:及时释放不再使用的对象
  3. 缓存策略:使用 Redis/Memcached 缓存数据
  4. 连接池:数据库连接复用
  5. 集群模式:多进程利用多核 CPU
  6. 负载均衡:分发请求到多个实例
  7. 监控告警:实时监控性能指标

内存泄漏检测

// 使用 --inspect 参数
node --inspect app.js

// 使用内存分析工具
const { performance, memoryUsage } = require('perf_hooks');

setInterval(() => {
  const mem = memoryUsage();
  console.log(`内存使用:
    RSS: ${Math.round(mem.rss / 1024 / 1024)}MB
    HeapTotal: ${Math.round(mem.heapTotal / 1024 / 1024)}MB
    HeapUsed: ${Math.round(mem.heapUsed / 1024 / 1024)}MB
    External: ${Math.round(mem.external / 1024 / 1024)}MB`);
}, 5000);

// 使用 heapdump
const heapdump = require('heapdump');
setInterval(() => {
  const filename = `heapdump-${Date.now()}.heapsnapshot`;
  heapdump.writeSnapshot(filename, (err) => {
    if (err) console.error(err);
    else console.log(`堆快照已保存: ${filename}`);
  });
}, 60000);

常见内存泄漏原因

  1. 全局变量:意外创建全局变量
  2. 闭包:不当使用闭包保持引用
  3. 定时器:未清除的 setInterval/setTimeout
  4. 事件监听:未移除的事件监听器
  5. 缓存增长:无限增长的缓存

集群模式

const cluster = require('cluster');
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 {
  // 工作进程共享同一个端口
  const app = require('./app');
  app.listen(3000);
  console.log(`工作进程 ${process.pid} 启动`);
}

负载均衡策略

  1. 轮询(Round Robin):依次分配请求
  2. 最少连接(Least Connections):分配给连接数最少的服务器
  3. IP 哈希(IP Hash):根据客户端IP分配
  4. 加权轮询(Weighted Round Robin):根据服务器权重分配

使用 PM2 进程管理

# 安装
npm install -g pm2

# 启动应用
pm2 start app.js
pm2 start app.js -i max  # 集群模式

# 管理进程
pm2 list
pm2 monit
pm2 logs
pm2 restart app
pm2 stop app
pm2 delete app

# 配置
pm2 ecosystem

NodeJS 安全

安全防护措施

包含的题目: 65. NodeJS 的安全问题有哪些? 66. 如何防范 SQL 注入? 67. 如何防范 XSS 攻击? 68. 如何防范 CSRF 攻击? 69. Helmet 的使用 70. CORS 的配置

详细答案:

安全防护

输入验证

const validator = require('validator');

// 验证输入
const email = req.body.email;
if (!validator.isEmail(email)) {
  return res.status(400).json({ error: '邮箱格式错误' });
}

const sanitized = validator.escape(req.body.content); // 转义 HTML

SQL 注入防护

// 使用参数化查询
const sql = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(sql, [username, password], (err, results) => {
  // 处理结果
});

// 使用 ORM(如 Sequelize)
const user = await User.findOne({
  where: { username, password }
});

XSS 防护

// 设置安全头部
app.use(helmet());

// 转义用户输入
const escapeHtml = (text) => {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  return text.replace(/[&<>"']/g, m => map[m]);
};

CSRF 防护

const csrf = require('csurf');
const cookieParser = require('cookie-parser');

app.use(cookieParser());
app.use(csrf({ cookie: true }));

// 提供 CSRF token
app.get('/csrf-token', (req, res) => {
  res.json({ csrfToken: req.csrfToken() });
});

// 验证请求
app.post('/api/data', (req, res) => {
  // csurf 中间件已自动验证
  res.json({ success: true });
});

Helmet 安全头部

const helmet = require('helmet');
app.use(helmet());

// 自定义配置
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
}));

CORS 配置

const cors = require('cors');

// 基本配置
app.use(cors());

// 自定义配置
app.use(cors({
  origin: ['https://example.com', 'https://www.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400,
}));

// 动态配置
app.use((req, res, next) => {
  const allowedOrigins = ['https://example.com'];
  const origin = req.headers.origin;
  
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  }
  
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  
  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  
  next();
});

NodeJS 常见问题

问题诊断与解决

包含的题目: 71. 如何解决内存泄漏问题? 72. 如何解决事件循环阻塞问题? 73. 如何处理异步错误? 74. 如何解决端口占用问题? 75. 如何解决跨域问题?

详细答案:

内存泄漏解决

  1. 使用分析工具:Chrome DevTools、heapdump、clinic.js
  2. 代码审查:检查全局变量、闭包、定时器、事件监听器
  3. 压力测试:模拟高并发场景,观察内存变化
  4. 监控告警:设置内存阈值,自动报警

事件循环阻塞解决

// 避免 CPU 密集型任务阻塞
// ❌ 错误:同步计算密集型任务
app.get('/compute', (req, res) => {
  let result = 0;
  for (let i = 0; i < 1e9; i++) {
    result += i;
  }
  res.json({ result });
});

// ✅ 正确:使用 Worker Threads
app.get('/compute', async (req, res) => {
  const worker = new Worker('./compute-worker.js');
  worker.postMessage({ type: 'compute' });
  
  worker.on('message', (result) => {
    res.json({ result });
    worker.terminate();
  });
});

异步错误处理

// Promise 错误处理
asyncFunction()
  .then(result => {
    // 成功处理
  })
  .catch(error => {
    console.error('Promise 错误:', error);
    // 错误处理
  });

// async/await 错误处理
async function handleRequest() {
  try {
    const result = await asyncFunction();
    // 成功处理
  } catch (error) {
    console.error('async/await 错误:', error);
    // 错误处理
  }
}

// 全局错误处理
process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的 Promise 拒绝:', reason);
  // 记录日志或重启应用
});

process.on('uncaughtException', (error) => {
  console.error('未捕获的异常:', error);
  // 记录日志,优雅关闭
  process.exit(1);
});

端口占用解决

const server = app.listen(3000, () => {
  console.log('服务器启动');
});

// 优雅关闭
process.on('SIGTERM', () => {
  console.log('收到 SIGTERM 信号,优雅关闭');
  server.close(() => {
    console.log('服务器关闭');
    process.exit(0);
  });
  
  // 强制关闭超时
  setTimeout(() => {
    console.log('强制关闭');
    process.exit(1);
  }, 10000);
});

// 端口被占用时自动重试
function startServer(port) {
  const server = app.listen(port, () => {
    console.log(`服务器运行在端口 ${port}`);
  }).on('error', (err) => {
    if (err.code === 'EADDRINUSE') {
      console.log(`端口 ${port} 被占用,尝试 ${port + 1}`);
      startServer(port + 1);
    } else {
      console.error('服务器错误:', err);
    }
  });
}

startServer(3000);

性能问题诊断

  1. 使用性能分析工具

    node --prof app.js
    node --prof-process isolate-0xnnnnnnn-v8.log > processed.txt
    
  2. 监控关键指标

    • CPU 使用率
    • 内存使用情况
    • 请求响应时间
    • 错误率
  3. 日志记录

    const morgan = require('morgan');
    app.use(morgan('combined')); // 访问日志
    

以上是对 NodeJS 75 道面试题目的综合解答,涵盖了 NodeJS 基础、模块系统、事件循环、异步编程、文件系统、网络编程、进程管理、性能优化和安全等核心知识点。