Node.js 中使用 require 加载模块时发生了什么?
描述了 Node.js 中 require 函数在加载模块时执行的一系列步骤,包括缓存检查、模块解析、定位与加载、编译和执行模块以及更新缓存。
当在 Node.js 中使用 require
加载模块时,它会执行一个序列化的解析、加载和缓存过程:
- 检查缓存
首先判断模块是否已加载过:if (require.cache[modulePath]) { return require.cache[modulePath].exports; // 直接返回缓存的 exports 对象 }
如果已在缓存中,直接返回结果(提高性能,避免重复加载)。
- 解析模块标识符
基于传入的字符串路径或模块名进行分类处理:- 核心模块(如
require('fs')
):直接返回内置模块。 - 相对/绝对路径(如
require('./module')
):
按顺序尝试:- 查找无后缀的文件(直接作为 JS 解析)。
- 查找
.js
,.json
,.node
后缀文件(.json
直接解析为 JSON 对象)。
- 非路径模块(如
require('lodash')
):
从当前目录向上递归查找node_modules
文件夹:- 在
node_modules
内检查是否为核心模块。 - 搜索对应名称的子文件夹(加载入口默认为
index.js
或package.json
指定main
)。
- 在
- 核心模块(如
- 定位并加载模块内容
针对文件/文件夹进行加载:const moduleCode = fs.readFileSync(modulePath); // 读取文件
- 文件夹模块:
无package.json
时默认加载index.js
;有package.json
则加载main
指定文件。 - JSON文件:解析为 JSON 对象(仅配置加载)。
- 文件夹模块:
- 编译并执行模块
将加载的文件内容封装在立即调用的函数表达式中执行:const compiledWrapper = (function(exports, require, module, __filename, __dirname) { ... // 执行模块代码,设置 exports }); compiledWrapper(module.exports, require, module);
此过程创建模块隔离作用域(避免了全局污染),导出
exports
接口。 - 更新缓存并返回结果
模块执行后将导出对象(module.exports
)加入缓存:require.cache[modulePath] = { exports: module.exports, loaded: true }; return module.exports; // 提供出口给调用方
关键特性:
- 同步加载:每次
require
时会阻塞后续代码执行直到模块完全加载(需避免大型模块阻塞)。 - 单例缓存:模块首次加载后会被缓存(所有后续
require
共享实例),缓存可手动清理(如动态热重载优化)。 - 错误处理:若路径不合法/文件不存在抛出
MODULE_NOT_FOUND
异常。