解释 Node.js 中的模块系统是如何工作的

Node.js 通过 require / module.exports(CommonJS)或 import / export(ESM)实现模块化,每个文件就是一个独立作用域。

来源整理自:vue3js.cn 面试官系列

1. 简述

  • 模块加载顺序:缓存命中 → 核心模块 → 相对/绝对路径文件 → node_modules 向上查找。
  • 模块在加载时被包进一个函数中执行,参数注入 exports、require、__filename、__dirname(CommonJS)。
  • 加载过的模块会缓存到 require.cache,二次 require 直接返回缓存对象。
  • ESM 与 CommonJS 同时存在时,模块默认走 ESM,require() CommonJS 时会被转换为 ESM 默认导出。

2. 背景知识

(1)模块的定义

在 Node.js 中,模块是实现特定功能的代码集合,每个文件都可以看作一个独立的模块。模块有助于代码的组织和复用,避免全局命名冲突。

(2)模块的分类

  • 核心模块:Node.js 自带的模块,如 fs(文件系统)、http(HTTP 服务器)等,在编译时就已经被编译进二进制文件中,加载速度快。
  • 文件模块:用户自己编写的模块,以 .js、.json 或 .node 为扩展名的文件。
  • 第三方模块:通过 npm(Node Package Manager)安装的模块,通常位于 node_modules 目录下。

3. 解析

(1)模块的查找机制

  • 核心模块:当使用 require 加载核心模块时,Node.js 会直接从内置的模块列表中查找并加载,无需查找文件系统。例如 const fs = require("fs");。
  • 文件模块:如果 require 的参数是相对路径(以 ./ 或 ../ 开头)或绝对路径,Node.js 会根据路径查找对应的文件。如果没有指定扩展名,Node.js 会按 .js、.json、.node 的顺序依次尝试加载,如果都不匹配会将其看作文件夹,寻找 index.js 文件。例如 const myModule = require("./myModule");。
  • 第三方模块:当 require 的参数不是核心模块,也不是相对或绝对路径时,Node.js 会从当前模块所在目录开始,逐级向上查找 node_modules 目录,直到根目录。例如 const express = require("express");。

(2)模块的加载过程

  • 解析路径:根据 require 的参数,使用上述查找机制确定模块的文件路径。
  • 创建模块对象:为每个模块创建一个 module 对象,该对象包含模块的元信息,如 id、exports 等。
  • 编译和执行:将模块的代码包装在一个函数中执行,函数的参数包括 exports、require、module 等,通过这些参数可以实现模块的导出和导入。
  • 缓存模块:为了提高性能,Node.js 会将加载过的模块缓存起来。当再次 require 同一个模块时,会直接从缓存中获取,而不会重新加载和执行。

(3)模块的导出和导入

  • 导出模块:在模块内部,可以使用 exports 或 module.exports 导出模块的内容。exports 是 module.exports 的引用,通常用于导出多个属性或方法。
  • 直接给 exports = { ... } 赋值会断开与 module.exports 的引用,导致导出失败;这是最常见的踩坑。

5. 常见误区

(1)混淆 exports 和 module.exports

误区:认为 exports 和 module.exports 可以随意互换使用,不清楚它们之间的区别。

纠正:exports 是 module.exports 的引用,module.exports 才是真正导出的对象。如果直接给 exports 赋值,会断开与 module.exports 的引用,导致导出失败。应该使用 module.exports 来导出单个值或对象。

(2)忽略模块缓存

误区:认为每次 require 同一个模块时都会重新加载和执行模块代码。

纠正:Node.js 会缓存加载过的模块,再次 require 时会直接从缓存中获取,不会重新加载和执行。如果需要重新加载模块,可以删除 require.cache 中的对应条目。

(3)不理解模块查找机制

误区:在使用 require 加载模块时,不清楚模块的查找顺序,导致找不到模块。

纠正:了解核心模块、文件模块和第三方模块的查找机制,正确使用相对路径、绝对路径和模块名来加载模块。