Skip to content
On This Page

面试题[Nodejs]

1. Node.js 基础

1.1 什么是 Node.js

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

简单总结:Node.js 让 JavaScript 从“前端脚本语言”蜕变为“服务端通用开发语言”。

  1. 核心特点
    • 异步非阻塞 I/O:采用事件驱动模型,处理高并发请求时不会因等待磁盘/网络 I/O 而阻塞线程,适合 I/O 密集型应用(如 API 服务、实时聊天、文件处理)。
    • 单线程 + 事件循环:主线程单线程运行,通过事件循环和回调/Promise/async/await 管理异步任务,避免多线程的锁和上下文切换开销。
    • 跨平台:支持 Windows、Linux、macOS。
    • npm:全球最大的开源包管理器,拥有丰富的第三方库,极大提升开发效率。
  2. 常见应用场景
    • Web 后端(Express、Koa、Nest.js)
    • 实时通信(WebSocket、Socket.io
    • 命令行工具(CLI)
    • 桌面应用(Electron 使用 Node.js 作为后端)
    • 中间层(BFF,Backend for Frontend)
  3. 与浏览器中 JavaScript 的区别
    • 全局对象:Node.js 中是 global,浏览器中是 window
    • 模块系统:Node.js 原生支持 CommonJS(require)和 ES Modules(import);浏览器依赖 <script> 或打包工具。
    • API:Node.js 提供文件系统(fs)、进程(process)、网络(http)等系统级 API,浏览器则聚焦 DOM/BOM 操作。

1.2 什么是 npm

npmNode Package Manager 的缩写,是 Node.js 的默认包管理器,也是全球最大的开源代码库(注册中心)。

  1. 主要功能:

    • 安装依赖:通过 npm install <package> 为项目安装第三方包。
    • 管理版本:在 package.json 中记录依赖及其版本范围,确保环境一致性。
    • 脚本运行:通过 npm run <script> 执行自定义命令(如启动、测试、构建)。
    • 发布包:开发者可以将自己的模块发布到 npm registry,供他人使用。
  2. 常见命令

    bash
    npm init          # 初始化项目,生成 package.json
    npm install       # 安装所有依赖
    npm install express --save   # 安装并保存到 dependencies
    npm install -g nodemon       # 全局安装命令行工具
    npm update        # 更新依赖
    npm publish       # 发布包

1.3 什么是 package.json

package.json 是 Node.js 项目的核心配置文件,位于项目根目录,使用 JSON 格式描述项目的元信息和依赖关系。

  1. 主要作用
    • 管理依赖:列出项目所需的所有第三方包及其版本范围(dependenciesdevDependencies 等)。
    • 定义脚本:通过 scripts 字段定义常用命令(如 npm startnpm test)。
    • 记录项目信息:包括项目名称、版本、作者、许可证等。
    • 锁定入口:通过 main 字段指定模块的入口文件。
  2. 如何生成
    • 运行 npm init 交互式创建,或 npm init -y 快速生成默认配置。
  3. 重要性
    • 确保项目在不同环境中安装相同的依赖版本(配合 package-lock.json)。
    • 是分享、发布和部署 Node.js 项目的基础文件。

1.4 Node.js 全局对象

在 Node.js 中,全局对象global,它类似于浏览器中的 window,但存在一些重要差异。

  1. 核心全局对象:global

    • 在任何模块中都可以直接访问 global 及其属性。
    • 在 Node.js REPL(交互式环境)中,顶层作用域就是 global
    • 注意:在普通模块文件中,使用 varletconst 声明的变量不会自动挂载到 global 对象上(模块作用域),这与浏览器不同。
  2. 常用的全局对象/属性

    以下变量和方法无需 require 即可直接使用:

    名称说明
    __dirname当前模块所在的目录的绝对路径
    __filename当前模块文件的绝对路径
    console用于打印日志(logerrorwarn 等)
    process提供当前 Node.js 进程信息和控制(如 process.envprocess.argvprocess.exit()
    Buffer用于处理二进制数据
    setTimeout / clearTimeout定时器(与浏览器类似)
    setInterval / clearInterval循环定时器
    setImmediate / clearImmediate立即执行回调(在 I/O 回调之前执行)
    globalThisES2020 标准,跨平台统一全局对象(Node.js 中等于 global

1.5 Node.js REPL

REPLRead-Eval-Print Loop 的缩写,是 Node.js 自带的交互式编程环境

在终端输入 node 命令(不加任何文件参数)即可进入 REPL 环境:

bash
$ node
>
  1. 工作流程
    1. Read:读取用户输入的 JavaScript 代码。
    2. Eval:在当前的上下文中执行代码。
    3. Print:打印执行结果(如表达式返回值、变量值)。
    4. Loop:循环上述过程,直到用户退出(按两次 Ctrl+C 或输入 .exit)。
  2. 常见用法
    • 快速测试 JavaScript 语法、函数、正则表达式。
    • 探索 Node.js 内置模块(如 fshttp)。
    • 运行多行代码(自动检测未闭合的花括号、括号等)。
    • 使用下划线 _ 获取上一次表达式的值。

1.6 Node.js 中间件

Node.js 中间件是在请求(Request)和响应(Response)生命周期中,用于处理、修改或终止流程的函数。它可以看作请求到达最终处理程序前的一系列处理工序,或请求处理流程中的“切面”。

  1. 中间件的工作机制

    • 在Express、Koa等框架中,中间件通过函数栈(或“洋葱模型”)工作。开发者通过 app.use() 注册的函数,会按注册顺序依次处理请求。
    • 一个典型的中间件函数接收 requestresponsenext 三个参数。调用 next() 会将控制权传递给下一个中间件;如果不调用,请求-响应周期就在此终止。
  2. 中间件的主要作用

    中间件的优势在于让应用开发更模块化和易于维护:

    • 执行任意代码:在请求处理前后,执行日志记录、身份验证、数据校验等通用逻辑。
    • 修改请求和响应对象:在流程中给 reqres 对象添加属性或方法,供后续中间件使用。
    • 终止请求-响应周期:当请求不符合条件(如身份验证失败)时,直接向客户端发送响应,不再继续向下执行。
    • 调用下一个中间件:通过 next() 函数,决定是否将控制权传递给流程中的下一个中间件。
  3. 常用的Node.js中间件

    类别中间件描述代码示例官方/相关链接
    请求体解析express.json()内置中间件,用于解析 Content-Typeapplication/json 的请求体,解析后的结果在 req.body 中。app.use(express.json());Express 官方文档
    express.urlencoded()内置中间件,用于解析表单提交的 URL 编码数据,解析结果在 req.body 中。app.use(express.urlencoded({ extended: true }));Express 官方文档
    body-parser早期的第三方中间件,用于解析多种格式的请求体。在 Express 4.16+ 之后,其功能已被内置替代。npm install body-parserbody-parser
    日志记录morganHTTP 请求日志中间件,可灵活配置输出格式(开发用 dev,生产用 combined 等),便于调试和监控。npm install morganmorgan
    winstonpino功能更全面的通用日志库,常与 morgan 配合使用,将请求日志输出到文件或外部系统。npm install winstonwinston
    安全helmet通过设置一系列安全的 HTTP 头,帮助应用抵御常见的 Web 漏洞(如 XSS、点击劫持等),提升安全性。npm install helmethelmet
    cors处理跨域资源共享,是构建前后端分离应用的必需中间件。npm install corscors
    身份验证passport极其强大的身份认证中间件,通过“策略”支持多种登录方式(本地、OAuth、JWT 等),提供灵活的非侵入式集成。npm install passportpassport
    文件上传multer专门用于处理 multipart/form-data 类型的表单数据,即文件上传。npm install multermulter
    静态文件express.static()内置中间件,将指定目录下的静态文件(如图片、CSS、JS 文件)直接提供给客户端访问。app.use(express.static('public'));Express 官方文档
    性能监控response-time记录服务器响应请求所花费的时间,可在响应头中添加 X-Response-Time 字段。npm install response-timeresponse-time
    compression对响应体进行 Gzip 等压缩,可有效减少传输数据量,提高应用性能。npm install compressioncompression
    辅助工具cookie-parser解析客户端请求中的 Cookie,方便开发者以 JavaScript 对象的形式读取和处理。npm install cookie-parsercookie-parser
    express-session简化会话管理,通过中间件提供用户会话数据的创建、读取和存储。npm install express-sessionexpress-session
  4. 如何自定义中间件

    自定义中间件是一个接收 req, res, next 的函数。在 Express 中使用 app.use()router.use() 即可注册。以下是两个常见例子:

    • 简单日志中间件:记录请求的方法和 URL,然后调用 next() 继续。

      javascript
      function logger(req, res, next) {
        console.log(`${req.method} ${req.url}`);
        next(); // 放行
      }
      app.use(logger);
    • 身份验证中间件:检查请求头 Authorization,验证不通过则直接 return 终止请求,否则放行。

      javascript
      function authenticate(req, res, next) {
        const token = req.header('Authorization');
        if (!token || token !== 'my-secret-token') {
          return res.status(401).json({ message: 'Unauthorized' });
        }
        next();
      }
      app.use(authenticate);

1.7 Node.js 模块加载机制

Node.js 中的模块加载机制基于 CommonJS 规范(同时逐步支持 ES Modules),核心是 require 函数,负责加载、缓存和执行模块。

核心流程(以 CommonJS 为例)

  1. 路径解析

    require('moduleName') 按以下顺序查找模块:

    • 核心模块:如 fshttp,直接返回。
    • 相对路径./..// 开头的文件模块,根据扩展名补全(.js.json.node)。
    • 第三方模块:从当前目录的 node_modules 开始,逐级向上查找至根目录。
    • 目录作为模块:查找目录下的 package.jsonmain 字段,或默认 index.js
  2. 加载与编译

    找到文件后,Node.js 根据扩展名处理:

    • .js:读取文件内容,包裹在函数中(见下),然后执行。
    • .jsonJSON.parse 后直接返回。
    • .node:C++ 插件,通过 process.dlopen 加载。

    每个模块文件在执行前会被包裹成一个函数:

    javascript
    (function(exports, require, module, __filename, __dirname) {
      // 模块代码
    });

    这保证了模块内的变量不会泄漏到全局。

  3. 缓存

    • require.cache 对象保存已加载模块的导出结果。
    • 重复 require 同一模块时,直接返回缓存,不会再次执行
  4. 导出与导入

    • module.exportsexports 定义模块对外接口。
    • require 返回的就是 module.exports 的值。

ES Modules (ESM)

Node.js 从 v12+ 开始支持原生 ESM(.mjs"type":"module".js),使用 import/export 语法,解析逻辑略有不同(强制文件扩展名、支持 URL 路径等),且默认异步加载。

1.8 Node.js 标准输入、输出和错误流

在 Node.js 中,标准输入(stdin)标准输出(stdout)标准错误(stderr) 是三个核心的 I/O 流,它们通过 process 对象暴露,用于与命令行环境交互。这三个流提供了与执行环境进行低开销文本交互的标准方式,是构建命令行 Node.js 程序的基础

  1. 流的性质
    • process.stdin可读流(Readable Stream),用于读取程序输入(如键盘输入、管道传来的数据)。
    • process.stdout可写流(Writable Stream),用于输出正常的程序结果。
    • process.stderr可写流,用于输出错误或诊断信息。
  2. 异步与同步注意事项
    • process.stdout.write同步的(在输出到文件或终端时可能表现为异步,但通常无需关心)。
    • 读取 stdin 总是异步的,需通过事件或流 API 处理。
  3. 实际应用场景
    • CLI 工具:使用 stdin 接收用户命令参数或管道数据。
    • 日志系统:将普通日志输出到 stdout,错误日志输出到 stderr,便于运维分离。
    • 子进程通信:父进程通过子进程的 stdin/stdout 管道进行 IPC。

1.9 Node.js 事件循环

Node.js 中的事件循环是它实现非阻塞 I/O异步操作的核心机制。它允许 Node.js 在单线程下处理大量并发请求,而不会因为等待磁盘、网络等操作而阻塞主线程。

简单理解:事件循环就像一个永不停止的“调度员”,它不断检查各种任务队列(比如定时器回调、I/O 回调、setImmediate 回调等),并按照固定的阶段顺序依次执行这些回调。

  1. 事件循环的工作流程(简化版)

    Node.js 启动时,会初始化事件循环。每次执行一个“滴答”(tick),它都会依次经过以下 6 个阶段

    ~
       ┌───────────────────────────┐
    ┌─>│           timers          │  执行 setTimeout / setInterval 的回调
    │  └─────────────┬─────────────┘
    │  ┌─────────────┴─────────────┐
    │  │     pending callbacks     │  执行延迟到下一次循环的 I/O 回调
    │  └─────────────┬─────────────┘
    │  ┌─────────────┴─────────────┐
    │  │       idle, prepare       │  内部使用,忽略
    │  └─────────────┬─────────────┘
    │  ┌─────────────┴─────────────┐
    │  │           poll            │  最关键阶段:检索新的 I/O 事件,执行 I/O 回调
    │  └─────────────┬─────────────┘
    │  ┌─────────────┴─────────────┐
    │  │           check           │  执行 setImmediate 回调
    │  └─────────────┬─────────────┘
    │  ┌─────────────┴─────────────┐
    └──┤      close callbacks      │  执行关闭回调(如 socket.on('close'))
       └───────────────────────────┘

    每个阶段都有一个先进先出的回调队列。事件循环会清空当前阶段的队列,或者执行到系统设定的最大限制后,再进入下一阶段。

    每轮循环按顺序经历以下阶段:

    阶段一:timers(定时器)

    • 检查 setTimeoutsetInterval 的回调队列。
    • 执行所有已到期的定时器回调(会尽可能多地执行,直到队列清空或达到系统限制)。
    • 注意:定时器只是“尽可能快”地执行,实际执行时间取决于后续阶段是否空闲。

    阶段二:pending callbacks(待定回调)

    • 执行某些系统操作的回调(例如 TCP 连接错误等)。普通开发者一般不需要直接操作此阶段。

    阶段三:idle, prepare(内部使用)

    • 仅供 libuv 内部使用,可以忽略。

    阶段四:poll(轮询)⭐ 最核心的阶段

    • 如果 poll 队列中有回调:依次同步执行,直到队列清空或达到硬性限制。
    • 如果 poll 队列为空
      • 如果有 setImmediate 回调等待,则立即跳过 poll 等待,进入下一阶段(check 阶段)。
      • 如果没有 setImmediate 等待,则阻塞在此阶段,等待新的 I/O 事件到来。一旦有 I/O 完成,其回调会被加入 poll 队列并立即执行。
      • 等待时还可以设置一个超时时间(通常是当前定时器中最早到期的时长),避免永久阻塞。
    • poll 阶段是处理文件 I/O、网络 I/O 回调的主要场所。

    阶段五:check(检查)

    • 专门执行 setImmediate 的回调函数。这些回调会在 poll 阶段空闲或完成后被立即执行。

    阶段六:close callbacks(关闭回调)

    • 执行关闭事件的回调,例如 socket.on('close', ...)process.on('exit', ...)
  2. 典型误区澄清

    • “事件循环是持续运行的”:不,当没有更多待处理的任务时,事件循环会退出,Node.js 进程结束。
    • setTimeout(fn, 0) 就一定比 setImmediate 快”:在事件循环启动时,timers 阶段先于 check 阶段,但 setTimeout(fn, 0) 的触发受系统计时器精度影响(通常为 1ms)。如果在主代码中连续调用,可能 setImmediate 更快。
    • process.nextTick 属于事件循环的一个阶段”:不,它是在每个阶段之间检查并执行的独立队列,不属于任何阶段。
    • 事件循环开始时间:同步代码已执行完毕,事件循环开始。
    • 事件循环的核心:在不阻塞主线程的前提下,调度不同优先级的异步回调。
    • 阶段顺序固定:timers → pending → poll → check → close,循环往复。
    • poll 阶段是主力:它负责等待和分发绝大多数 I/O 回调。
    • setImmediate 在 check 阶段执行,比 setTimeout(fn, 0) 在计时器阶段执行,但启动时因计时器精度问题顺序可能不确定。
    • 微任务插队process.nextTickPromise 回调会在每个阶段之间优先执行,nextTick 优先级高于 Promise
    • 递归调用危险:在 nextTick 中无限递归会永久阻塞事件循环;setImmediate 中递归则相对安全,因为每次会先让出控制权给其他阶段。

2. Node.js 进阶

如何在 Node.js 中

2.1 创建 HTTP 服务器

在 Node.js 中,可以使用内置的 http 模块快速创建一个 HTTP 服务器。以下是最简单的示例:

javascript
// 1. 引入 http 模块
const http = require('http');

// 2. 创建服务器实例
const server = http.createServer((req, res) => {
  // req: 请求对象(包含 url、method、headers 等)
  // res: 响应对象(用于返回数据)

  // 设置响应状态码和头信息
  res.writeHead(200, { 'Content-Type': 'text/plain' });

  // 发送响应内容并结束请求
  res.end('Hello World\n');
});

// 3. 监听端口(例如 3000)
const PORT = 3000;
server.listen(PORT, () => {
  console.log(`服务器已启动,访问 http://localhost:${PORT}`);
});

2.2 创建 TCP 服务器

在 Node.js 中创建 TCP 服务器可以使用内置的 net 模块。以下是完整的示例,包括服务器创建、事件处理以及客户端测试。

  1. TCP 服务器代码(server.js)

    javascript
    const net = require('net');
    
    // 创建 TCP 服务器
    const server = net.createServer((socket) => {
        console.log('客户端已连接,地址:', socket.remoteAddress, '端口:', socket.remotePort);
    
        // 接收客户端数据
        socket.on('data', (data) => {
            const message = data.toString().trim();
            console.log('收到消息:', message);
            
            // 回显消息给客户端
            socket.write(`服务器收到:${message}\n`);
        });
    
        // 客户端关闭连接
        socket.on('end', () => {
            console.log('客户端断开连接');
        });
    
        // 错误处理
        socket.on('error', (err) => {
            console.error('Socket 错误:', err.message);
        });
    });
    
    // 监听端口
    const PORT = 3000;
    server.listen(PORT, () => {
        console.log(`TCP 服务器已启动,监听端口 ${PORT}`);
    });
    
    // 服务器全局错误
    server.on('error', (err) => {
        console.error('服务器错误:', err);
    });
  2. 测试客户端(client.js)—— 使用 net 模块

    javascript
    const net = require('net');
    
    const client = net.createConnection({ port: 3000 }, () => {
        console.log('已连接到服务器');
        client.write('Hello, TCP Server!');
    });
    
    client.on('data', (data) => {
        console.log('服务器返回:', data.toString());
        client.end(); // 发送结束信号
    });
    
    client.on('end', () => {
        console.log('与服务器断开连接');
    });
    
    client.on('error', (err) => {
        console.error('连接错误:', err);
    });
  3. 运行方法

    • 启动服务器:node server.js
    • 另开终端运行客户端:node client.js

2.3 发送 HTTP 请求

在 Node.js 中发送 HTTP 请求有多种方式,从内置模块到第三方库,各有适用场景。

  1. 使用内置 http / https 模块(无依赖,但较底层)

    javascript
    const https = require('https');
    
    https.get('https://api.example.com/data', (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => {
        console.log(JSON.parse(data));
      });
    }).on('error', (err) => {
      console.error(err);
    });
    javascript
    const http = require('http');
    
    const postData = JSON.stringify({ name: 'John' });
    const options = {
      hostname: 'example.com',
      port: 80,
      path: '/api',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(postData)
      }
    };
    
    const req = http.request(options, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => console.log(data));
    });
    req.write(postData);
    req.end();
  2. 使用 node-fetch(轻量,API 与浏览器 fetch 一致)

  3. 使用 axios(功能最丰富,自动 JSON 解析,支持拦截器等)

2.4 获取命令行参数

在 Node.js 中,获取命令行参数主要通过 process.argv 属性实现。

process.argv 是一个数组,包含启动 Node.js 进程时的所有命令行参数:

  • 第 0 个元素:Node.js 可执行文件的绝对路径(通常为 node 命令的路径)。
  • 第 1 个元素:当前执行的 JavaScript 文件的绝对路径。
  • 从第 2 个元素开始:用户传入的自定义参数,以空格分隔。

简单示例:

javascript
// 创建 cli.js 文件:
// cli.js
console.log(process.argv);
bash
# 在终端执行:
$ node cli.js hello --name John 123
text
输出类似:
[
  '/usr/local/bin/node',
  '/path/to/cli.js',
  'hello',
  '--name',
  'John',
  '123'
]

处理键值对参数:手动解析 --key value--key=value 格式:

javascript
const args = process.argv.slice(2);
const parsed = {};
for (let i = 0; i < args.length; i++) {
  if (args[i].startsWith('--')) {
    const key = args[i].slice(2);
    const value = args[i + 1];
    parsed[key] = value;
    i++; // 跳过下一个参数(值)
  }
}
console.log(parsed); // { name: 'John' }

使用第三方库简化(推荐):对于复杂命令行工具,使用成熟的库可以省去手动解析的麻烦:

  • minimist:轻量级
  • yargs:功能强大,自动生成帮助信息
  • commander:流行的 CLI 框架

2.5 获取和设置环境变量

在 Node.js 中,环境变量通过全局对象 process.env 进行访问和修改。该对象包含了当前进程的环境变量,并且对其所做的更改只会影响当前进程及其子进程。

  1. 获取环境变量

    使用 process.env.变量名 即可读取,如果变量不存在则返回 undefined

    javascript
    // 获取环境变量
    const dbHost = process.env.DB_HOST;
    console.log(dbHost); // 例如 'localhost'
    
    // 带默认值
    const port = process.env.PORT || 3000;

    注意:变量名大小写敏感(在 Linux/macOS 上)但 Windows 不区分大小写。为了跨平台兼容,建议始终使用相同的大小写(通常为大写)。

  2. 设置环境变量

    直接给 process.env 的属性赋值,值会被转换为字符串。

    javascript
    // 设置环境变量
    process.env.NODE_ENV = 'production';
    process.env.API_KEY = 'abc123';
  3. 删除环境变量

    javascript
    delete process.env.TEMP_VAR;
  4. 检查环境变量是否存在

    javascript
    if ('DEBUG_MODE' in process.env) {
      console.log('调试模式已开启');
    }
  5. 使用 .env 文件管理环境变量(推荐)

    在实际项目中,通常使用 dotenv 包从 .env 文件加载环境变量。

    bash
    npm install dotenv

    创建 .env 文件:

    text
    DB_HOST=localhost
    DB_USER=root
    DB_PASS=s1mpl3

2.6 实现纳秒级别的高精度计时

Node.js 提供了多种实现高精度计时的方案,其中 process.hrtime.bigint() 是达到纳秒级(nanosecond, ns)精度的首选方法,精度可达1纳秒。

API / 方法核心精度返回类型关键特性主要使用场景
Date.now()毫秒级 (ms)数字 (Number)通用时间戳,受系统时钟影响日常日志、数据库时间戳
console.time()毫秒级 (ms)控制台输出语法简单,快速调试开发阶段简单性能评估
performance.now()微秒级 (μs)数字 (Number)高精度、单调递增、跨平台一致常规性能测试、动画帧计时
process.hrtime.bigint()纳秒级 (ns)大整数 (BigInt)最高精度、单调递增、不受时钟调整影响基准测试、微性能分析
perf_hooks 模块纳秒级 (ns)事件对象提供 performance.mark, measure 等高级API复杂性能追踪与分析

2.7 创建和解析 URL

在 Node.js 中处理 URL 主要有两种方式:旧版 url 模块的 parse 方法(现在标记为遗留)和更现代、更符合 Web 标准的 WHATWG URL API。强烈推荐使用后者。

  1. 使用 WHATWG URL API(推荐)

    Node.js 从 v7.0.0 开始支持标准的 URLURLSearchParams 全局类,无需额外引入模块。

    javascript
    // 解析 URL
    const urlString = 'https://user:pass@example.com:8080/path/to/page?name=Alice&age=25#section';
    const myUrl = new URL(urlString);
    
    console.log(myUrl.protocol);   // 'https:'
    console.log(myUrl.username);   // 'user'
    console.log(myUrl.password);   // 'pass'
    console.log(myUrl.hostname);   // 'example.com'
    console.log(myUrl.port);       // '8080'
    console.log(myUrl.pathname);   // '/path/to/page'
    console.log(myUrl.search);     // '?name=Alice&age=25'
    console.log(myUrl.hash);       // '#section'
    console.log(myUrl.origin);     // 'https://example.com:8080'

    创建 / 拼接 URL

    javascript
    const base = 'https://api.example.com/v1/';
    const endpoint = new URL('users/123', base);
    endpoint.searchParams.set('expand', 'profile');  // 添加查询参数
    
    console.log(endpoint.href); 
    // 'https://api.example.com/v1/users/123?expand=profile'

    解析查询字符串(URLSearchParams)

    javascript
    const params = myUrl.searchParams;
    params.get('name');     // 'Alice'
    params.has('age');      // true
    params.getAll('hobby'); // 如果没有 hobby,返回 []
    
    // 遍历所有参数
    for (const [key, value] of params) {
      console.log(`${key}=${value}`);
    }
    // 输出:
    // name=Alice
    // age=25
    
    // 也可以直接操作 URL 对象
    myUrl.searchParams.append('city', 'NYC');
    console.log(myUrl.href); 
    // ...原 URL...&city=NYC
  2. 旧版 url.parse()(不推荐用于新项目)

    旧接口存在于 url 模块中,可能会在未来版本中被废弃。用法如下:

    javascript
    const url = require('url');
    
    const parsed = url.parse('https://example.com/path?foo=bar#anchor', true); 
    // 第二个参数 true 表示自动解析查询字符串为对象
    
    console.log(parsed.host);      // 'example.com'
    console.log(parsed.query);     // { foo: 'bar' }
    console.log(parsed.pathname);  // '/path'

    但旧 API 有以下不足:

    • 不符合浏览器标准
    • user:pass 等部分的处理不可靠
    • 默认不处理 Unicode 和 IDNA

2.8 执行外部命令或脚本

在 Node.js 中执行外部命令或脚本,主要使用内置的 child_process 模块。该模块提供了多个方法,适用于不同场景。

方法回调风格返回数据方式适用场景
exec回调一次性全部返回 stdout/stderr(缓冲)命令输出量小、希望简单获取字符串
execFile回调一次性全部返回 stdout/stderr(缓冲)直接执行可执行文件,不经过 shell(更安全)
spawn流式通过事件流式返回 stdout/stderr输出量大、需要实时处理或交互
fork流式 / IPC专门用于启动新的 Node.js 进程(支持消息通信)执行另一个 Node.js 模块,需进程间通信

安全最佳实践:

  1. 永远不要将未经过滤的用户输入直接拼接到 exec 的命令字符串中。
  2. 优先使用 execFilespawn,并显式传递参数数组
  3. 限制缓冲区大小(使用 maxBuffer 选项,默认 1MB)。
  4. 对于长时间运行的子进程,务必监听 errorclose 事件,并正确处理 stdout/stderr

安全示例(推荐):

javascript
const { spawn } = require('child_process');
const userInput = 'somefile.txt';
const ls = spawn('ls', ['-l', userInput]);  // 用户输入作为独立参数,不会被当做命令执行

2.9 process.nextTick() 和 setImmediate() 的区别

在 Node.js 中,process.nextTick()setImmediate() 都用于将回调函数的执行推迟到当前操作完成后,但它们在事件循环中的执行时机和优先级有本质区别。

特性process.nextTick()setImmediate()
执行阶段当前操作结束,事件循环继续之前事件循环的 check 阶段
执行顺序优先于任何 I/O 或计时器回调在当前事件循环迭代的末尾执行
递归风险容易导致事件循环饥饿(递归调用会阻塞 I/O)相对安全,不会阻塞
适用场景立即处理错误、在调用栈清空后继续执行将任务推迟到下一个事件循环迭代

形象理解nextTick 像是在当前代码执行完后的“插队”(仍在同一轮事件循环中),而 setImmediate 则是“下一轮再处理”。尽管名字相反,但 nextTicksetImmediate 要“立即”得多。

  1. process.nextTick() 适用场景
    • 在异步 API 中确保回调异步执行:例如将同步回调转为异步,避免调用顺序意外。类似 process.nextTick(callback) 能保证回调在当前代码执行完毕后、任何 I/O 之前触发。
    • 延迟执行但要求尽快:如在构造函数中触发事件,但要确保监听器已注册。
    • 错误处理和资源清理:在同步代码中发生错误后,尽快执行清理逻辑。
  2. setImmediate() 适用场景
    • 将大型任务拆分到下一个事件循环迭代,防止阻塞 I/O。
    • 需要真正“稍后执行”,而不是立刻挤占当前事件循环的后续阶段。
    • 与 I/O 操作配合:例如在文件读取完成后,需要执行后续逻辑但不希望阻塞其他 I/O。

3. Node.js 拓展

3.1 Node.js 应用的安全问题

确保 Node.js 应用程序安全是一场需要贯穿在整个开发和部署生命周期中的持久战。安全的基本原则是 纵深防御 (Defence in Depth),即不依赖任何单一措施,而是通过多层次、多样化的防护手段来构建一个稳固的整体防御体系。

Node.js 应用的安全问题可归为两大类:易被攻击的漏洞不安全的配置与编码。以下是一套系统性的方案清单,并附有具体的防护措施。

风险类别主要安全问题核心应对策略相关框架与库
注入攻击 (SQL, NoSQL, 命令注入)攻击者通过用户输入注入恶意代码,操控或破坏数据库或服务器。1. 使用参数化查询或ORM,确保输入被当作数据,而非代码。 2. 使用schema验证库,对所有用户输入进行严格的类型校验。 3. 避免使用exec,改用execFilespawn并传递参数数组。验证: Zod, Joi, Yup, Valibot, ArkType, express-validator ORM/ODM: Sequelize, TypeORM, Prisma, Mongoose 安全: @arcis/node, secure-shield
跨站脚本 (XSS)恶意脚本被注入网页,窃取用户数据或篡改页面内容。1. 对用户输出进行上下文转义/编码。 2. 使用安全的模板引擎,或DOMPurify等清理库。 3. 实施严格的内容安全策略 (CSP),限制可加载的资源。输出转义: DOMPurify, validator库的escape()方法, xss 安全头: helmet, koa-helmet
身份认证与授权失效认证/授权机制存在漏洞,导致攻击者可冒充用户或越权操作。1. 使用bcrypt等安全算法哈希并加盐存储密码。 2. 对会话ID设置HttpOnlySecureSameSite属性。 3. 实施严格的基于角色的访问控制 (RBAC),并验证资源所有权。认证/授权: passport, jsonwebtoken, bcrypt, accesscontrol, CASL
安全配置错误系统、框架或中间件的默认配置不安全或开启调试信息。1. 使用helmet中间件,快速设置安全的HTTP头。 2. 在生产环境关闭错误堆栈信息。 3. 严格配置corsHTTP安全头: helmet, koa-helmet, fastify-helmet 反参数污染: hpp CORS: cors
敏感数据暴露敏感数据(如密码、API密钥)明文存储或传输。1. 强制使用HTTPS加密传输。 2. 敏感信息通过环境变量注入,禁止硬编码。 3. 使用bcryptscrypt存储密码。密码加密/哈希: bcrypt, scrypt 环境变量: dotenv-safe, dotenv
资源耗尽与暴力破解攻击者通过大量请求耗尽服务器资源 (DoS) 或暴力破解密码。1. 对API和敏感端点实施速率限制 (Rate Limiting)。 2. 限制请求体大小,防止大负载攻击。 3. 分布式部署时,使用Redis实现共享限流。速率限制核心库: express-rate-limit, rate-limit-redis 高级限流: express-slow-down
不安全的依赖包项目中使用了包含已知漏洞的第三方npm包。1. 运行npm audit定期检查并修复漏洞。 2. 在CI流程集成npm auditSnyk。 3. 使用静态安全测试 (SAST) 工具扫描代码模式。依赖扫描: npm audit, Snyk, DepGuard, NodeSecure CLI 代码/CI扫描: njsscan, semgrep, audit-ci, secure-shield
跨站请求伪造 (CSRF)诱导用户在已认证的Web应用上执行非本意的操作。1. 使用CSRF防护中间件 2. 验证同源请求头 3. 实施双重提交Cookie模式框架集成: lusca (Express), @arcis/node (内置CSRF)

一键式综合防护框架:如果你希望更快、更简单的集成,上述许多防护手段已经可以被部分综合性框架所覆盖:

框架/中间件功能描述
@arcis/node号称“一行代码”的Node.js安全中间件,可处理SQL/NoSQL注入、XSS、命令注入、路径遍历、原型污染等16+种攻击向量。
secure-shield综合性安全中间件,提供SQL/NoSQL注入检测与防御、XSS防护、速率限制、请求验证和安全头管理等。
@risingstack/protect为Express提供开箱即用的保护,包括SQL注入、XSS、限流和安全头设置。
helmet专注HTTP安全头,设置CSP、HSTS、X-Frame-Options等十几项头信息。
luscaExpress专用,提供CSRF、XSS防护、点击劫持、CSP等中间件。