Node.js 模块系统详解:从基础使用到加载机制源码解析
本文深入解析 Node.js 模块系统,涵盖模块的基本概念、创建与导出方式(exports 与 module.exports),以及内置模块与文件模块的加载策略。重点剖析 require 方法的文件查找优先级、路径解析算法及核心加载函数,帮助开发者理解模块加载的内部逻辑。
Node.js 模块系统概述
为了实现 Node.js 中文件间的相互调用,Node.js 内置了一套简洁的模块系统。
模块是 Node.js 应用的核心构成单元,每个文件与一个模块严格对应。这意味着,无论是 JavaScript 代码、JSON 数据文件,还是编译后的 C/C++ 扩展文件,在 Node.js 中均被视为一个独立的模块。
模块的创建与导出
创建模块
在 Node.js 中创建模块十分便捷。以下示例展示了如何创建一个名为 main.js 的文件,其核心代码如下:
var hello = require('./hello');
hello.world();
上述代码中,require('./hello') 语句用于引入当前目录下的 hello.js 文件。其中 './' 代表当前目录,Node.js 默认文件后缀为 .js。
Node.js 提供了 exports 和 require 两个关键对象。exports 用于定义模块对外公开的接口,而 require 则用于从外部引入模块,即获取该模块的 exports 对象。
接下来我们创建 hello.js 文件,具体代码如下:
exports.world = function() {
console.log('Hello World');
}
在示例中,hello.js 通过 exports 对象暴露 world 函数作为模块访问接口。在 main.js 中,通过 require('./hello') 加载该模块后,即可直接调用 hello.js 中 exports 对象所挂载的成员函数。
有时我们需要将整个对象封装为模块接口,其格式如下:
module.exports = function() {
// ...
}
例如:
//hello.js
function Hello() {
var name;
this.setName = function(thyName) {
name = thyName;
};
this.sayHello = function() {
console.log('Hello ' + name);
};
};
module.exports = Hello;
此时可直接获取该对象:
//main.js
var Hello = require('./hello');
hello = new Hello();
hello.setName('BYVoid');
hello.sayHello();
模块接口的关键变化在于使用 module.exports = Hello 替代了 exports.world = function(){}。在外部引用该模块时,其接口对象即为输出的 Hello 对象本身,而非默认的 exports 对象。
服务端模块的存放与引用
服务端模块的存放位置
你可能已在代码中使用了模块,例如:
var http = require("http");
...
http.createServer(...);
Node.js 内置了一个名为 http 的模块。我们在代码中通过 require 请求该模块,并将返回值赋给本地变量。
这使得本地变量成为一个包含 http 模块所有公共方法的对象。
模块加载机制与优先级
Node.js 的 require 方法在解析文件时遵循特定的查找策略:
由于 Node.js 包含 4 类模块(原生模块和 3 种文件模块),尽管 require 方法接口简单,但其内部加载逻辑复杂,且各类模块的加载优先级各不相同。具体流程如下图所示:

从文件模块缓存中加载
尽管原生模块与文件模块的优先级存在差异,但两者均会优先从文件模块的缓存中加载已存在的模块。
从原生模块加载
原生模块的优先级仅次于文件模块缓存。在 require 方法解析文件名后,会优先检查该模块是否位于原生模块列表中。以 http 模块为例,即使当前目录下存在 http/http.js、http.node 或 http.json 文件,require("http") 也不会加载这些文件,而是直接从原生模块中加载。
原生模块同样拥有缓存区,优先从缓存中加载。若缓存中未加载过,则调用原生模块的加载机制进行加载和执行。
从文件加载
当文件模块缓存中不存在目标,且非原生模块时,Node.js 会解析 require 传入的参数,并从文件系统中加载实际文件。加载过程中的包装和编译细节前文已介绍,此处重点详述文件模块的查找过程,其中包含若干值得注意的细节。
require 参数类型
require 方法接受以下几种参数传递:
- http、fs、path 等,属于原生模块。
- ./mod 或 ../mod,表示相对路径的文件模块。
- /pathtomodule/mod,表示绝对路径的文件模块。
- mod,非原生模块的文件模块。
模块查找算法详解
在路径 Y 下执行 require(X) 语句的执行顺序如下:
1. 如果 X 是内置模块 a. 返回内置模块 b. 停止执行 2. 如果 X 以 '/' 开头 a. 设置 Y 为文件根路径 3. 如果 X 以 './' 或 '/' or '../' 开头 a. LOAD_AS_FILE(Y + X) b. LOAD_AS_DIRECTORY(Y + X) 4. LOAD_NODE_MODULES(X, dirname(Y)) 5. 抛出异常 "not found"
LOAD_AS_FILE(X) 1. 如果 X 是一个文件, 将 X 作为 JavaScript 文本载入并停止执行。 2. 如果 X.js 是一个文件, 将 X.js 作为 JavaScript 文本载入并停止执行。 3. 如果 X.json 是一个文件, 解析 X.json 为 JavaScript 对象并停止执行。 4. 如果 X.node 是一个文件, 将 X.node 作为二进制插件载入并停止执行。
LOAD_INDEX(X) 1. 如果 X/index.js 是一个文件, 将 X/index.js 作为 JavaScript 文本载入并停止执行。 2. 如果 X/index.json 是一个文件, 解析 X/index.json 为 JavaScript 对象并停止执行。 3. 如果 X/index.node 是一个文件, 将 X/index.node 作为二进制插件载入并停止执行。
LOAD_AS_DIRECTORY(X) 1. 如果 X/package.json 是一个文件, a. 解析 X/package.json, 并查找 "main" 字段。 b. let M = X + (json main 字段) c. LOAD_AS_FILE(M) d. LOAD_INDEX(M) 2. LOAD_INDEX(X)
LOAD_NODE_MODULES(X, START) 1. let DIRS=NODE_MODULES_PATHS(START) 2. for each DIR in DIRS: a. LOAD_AS_FILE(DIR/X) b. LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START) 1. let PARTS = path split(START) 2. let I = count of PARTS - 1 3. let DIRS = [] 4. while I >= 0, a. if PARTS[I] = "node_modules" CONTINUE b. DIR = path join(PARTS[0 .. I] + "node_modules") c. DIRS = DIRS + DIR d. let I = I - 1 5. return DIRS
以上为 Node.js 模块系统的详细解析,更多关于 Node.js 模块系统的资料请关注本站其他相关文章!
怎么在本地运行JavaScript脚本:Node.js下载安装与环境配置教程
2026-11-05 09:02:33
JavaScript开发环境怎么搭建:Node.js安装与VS Code配置指南
2026-11-03 23:36:59
JavaScript零基础入门:环境搭建与第一个Hello World
2026-11-03 23:34:09
NW.js 是什么:基于 Chromium 和 Node.js 的桌面应用框架
2026-11-03 15:14:00
nvm、n 和官方安装包:哪种 Node.js 升级方式更适合你
2026-11-03 14:54:28
