Node.js 文件模块与核心模块详解:定位、编译与执行机制
本文深入解析 Node.js 中文件模块与核心模块的区别,涵盖从 require 导入到最终执行的完整生命周期。详细阐述文件模块的定位策略(含扩展名分析与模块路径递归)、编译执行过程(JS、JSON 及 C/C++ 扩展模块的处理),以及核心模块在进程启动时的内存加载机制与内建模块引用链,帮助开发者理解模块加载性能差异及底层实现原理。
前言
在 Node.js 日常开发中,通过 require 导入的模块主要分为两类:一类是开发者自行编写或通过 npm 安装的第三方模块,统称为 文件模块;另一类是 Node.js 内置提供的模块,例如 os、fs 等,这类模块被称为 核心模块。
文件模块与核心模块的区别不仅在于是否内置,更体现在文件定位、编译及执行过程的显著差异上。此外,文件模块还可细分为普通文件模块、自定义模块以及 C/C++ 扩展模块等,各类模块在定位与编译流程中存在诸多细节差异。
本文将梳理文件模块与核心模块的概念,并深入解析它们在文件定位、编译及执行流程中的具体步骤与关键细节,旨在为开发者提供清晰的技术参考。
首先从文件模块开始探讨。
文件模块
如何定义文件模块?
在 Node.js 中,若模块标识符以 .、.. 或 / 开头(即使用相对路径或绝对路径),通过 require 加载的模块均被视为文件模块。此外,还存在一类特殊模块:虽无路径前缀且非核心模块,但指向特定包。Node.js 在定位此类模块时,会依据 模块路径 进行逐个查找,这类模块被归类为自定义模块。
综上,文件模块包含两种类型:一是带有明确路径的普通文件模块,二是不带路径但通过模块路径查找的自定义模块。
文件模块在运行时动态加载,需经历完整的文件定位、编译及执行过程,因此其加载速度通常慢于核心模块。
针对文件定位,Node.js 对这两类文件模块的处理策略有所不同。接下来将详细解析这两类文件模块的具体查找流程。
普通文件模块的查找
对于普通文件模块,由于路径明确,查找耗时较短,效率高于下文介绍的自定义模块。但需注意以下两点细节。
首先,通常情况下使用 require 引入文件模块时不指定文件扩展名,例如:
const math = require("math");由于未指定扩展名,Node.js 无法直接确定目标文件。此时,Node.js 会按照 .js、.json、.node 的顺序依次补足扩展名并尝试加载,这一过程被称为 文件扩展名分析。
其次,实际开发中除了引入具体文件外,常需指定目录,例如:
const axios = require("../network");在此情况下,Node.js 先进行文件扩展名分析。若未找到对应文件但识别出目录,Node.js 会将该目录视为包进行处理。
具体而言,Node.js 会读取目录下的 package.json 文件,并提取其中的 main 字段所指向的文件作为查找结果。若 main 指向的文件有误或不存在 package.json 文件,Node.js 将使用 index 作为默认文件名,并依次尝试 .js 和 .node 进行扩展名分析,逐个查找目标文件;若仍无法找到,则抛出错误。
(注:Node.js 存在 CommonJS (CJS) 和 ES Modules (ESM) 两套模块系统,除查找 main 字段外,ESM 还有其他加载方式,因不在本文讨论范围内,此处不再赘述。)
自定义模块的查找
前文提及,Node.js 在查找自定义模块时会用到模块路径。那么,什么是模块路径?
熟悉模块解析机制的开发者应当知晓,模块路径是一个由路径组成的数组,具体值可参考以下示例:
// example.js
console.log(module.paths);打印结果:

可见,Node.js 中存在一个模块路径数组,存储于 module.paths 中,用于规定 Node.js 如何查找当前模块引用的自定义模块。
具体流程为:Node.js 遍历模块路径数组,逐个尝试路径,查找该路径对应的 node_modules 目录中是否存在指定的自定义模块。若未找到,则向上逐级递归,直至根目录下的 node_modules 目录。若最终仍未找到目标模块,则抛出错误。
由此可见,逐级向上递归查找 node_modules 目录是 Node.js 查找自定义模块的核心策略,而模块路径则是该策略的具体实现方式。
由此可得出一个结论:在查找自定义模块时,层级越深,查找耗时越长。因此,相比核心模块和普通文件模块,自定义模块的加载速度最慢。
此外,通过模块路径查找到的通常是一个目录而非具体文件。找到目录后,Node.js 会按照前述包处理流程进行最终的文件定位,具体过程不再赘述。
以上介绍了普通文件模块和自定义模块的文件定位流程及注意事项,接下来将探讨这两类模块的编译与执行过程。
文件模块的编译执行
当定位到 require 指向的文件后,通常模块标识符不带扩展名。根据前文提到的文件扩展名分析,Node.js 支持三种扩展名文件的编译执行:
- JavaScript 文件:通过
fs模块同步读取文件后编译执行。除.node和.json文件外,其他文件均被视为.js文件载入。 .node文件:这是由 C/C++ 编写并编译生成的扩展文件,Node.js 通过process.dlopen()方法加载该文件。- json 文件:通过
fs模块同步读取文件后,使用JSON.parse()解析并返回结果。
在对文件模块进行编译执行前,Node.js 会使用如下所示的模块封装器对其进行包装:
(function(exports, require, module, __filename, __dirname) {
// 模块代码
});通过模块封装器,Node.js 将模块代码包裹在函数作用域内,与其他作用域隔离,从而避免变量命名冲突及全局作用域污染。同时,通过传入 exports、require 等参数,使模块具备相应的导入与导出能力。这便是 Node.js 对模块机制的实现方式。
了解模块封装器后,我们先来看 json 文件的编译执行流程。
json 文件的编译执行
json 文件的编译执行过程最为简单。Node.js 通过 fs 模块同步读取 JSON 文件内容后,使用 JSON.parse() 解析为 JavaScript 对象,并将其赋值给该模块的 exports 对象,最后返回给引用它的模块,整个过程简洁直接。
JavaScript 文件的编译执行
在使用模块包装器对 JavaScript 文件进行包装后,包装后的代码会通过 vm 模块的 runInThisContext()(功能类似 eval)方法执行,返回一个 function 对象。
随后,将该 JavaScript 模块的 exports、require、module 等参数传递给该 function 执行。执行完毕后,模块的 exports 属性被返回给调用方,这便是 JavaScript 文件的完整编译执行过程。
C/C++ 扩展模块的编译执行
在讲解 C/C++ 扩展模块的编译执行前,先介绍什么是 C/C++ 扩展模块。
C/C++ 扩展模块属于文件模块的一种,顾名思义,这类模块由 C/C++ 编写。与 JavaScript 模块不同,其加载后无需再次编译,直接执行即可被外部调用,因此加载速度略快于 JavaScript 模块。相比纯 JS 编写的文件模块,C/C++ 扩展模块在性能上具有显著优势。对于 Node.js 核心模块无法覆盖的功能或有特定性能需求的场景,用户可编写 C/C++ 扩展模块以满足需求。
那么,.node 文件是什么?它与 C/C++ 扩展模块有何关系?
事实上,编写完成的 C/C++ 扩展模块经过编译后生成 .node 文件。也就是说,作为模块使用者,我们并不直接引入 C/C++ 扩展模块的源代码,而是引入其编译后的二进制文件。因此,.node 文件无需编译,Node.js 在查找到 .node 文件后,只需加载并执行该文件即可。在执行过程中,模块的 exports 对象被填充,随后返回给调用者。
值得注意的是,C/C++ 扩展模块编译生成的 .node 文件在不同平台下形式不同:在 *nix 系统下,C/C++ 扩展模块由 g++/gcc 等编译器编译为动态链接共享对象文件,扩展名为 .so;在 Windows 下则由 Visual C++ 编译器编译为动态链接库文件,扩展名为 .dll。但在实际使用中,我们看到的扩展名却是 .node。事实上,.node 的扩展名仅为视觉上的统一,实际上在 Windows 下它是一个 .dll 文件,在 *nix 下则是一个 .so 文件。
Node.js 在查找到需 require 的 .node 文件后,会调用 process.dlopen() 方法对该文件进行加载和执行。由于 .node 文件在不同平台下形式各异,为实现跨平台兼容,dlopen() 方法在 Windows 和 *nix 平台下分别有不同的实现,并通过 libuv 兼容层进行封装。
下图展示了 C/C++ 扩展模块在不同平台下的编译和加载过程:

核心模块
核心模块在 Node.js 源代码编译过程中,已被编译进二进制执行文件。在 Node.js 进程启动时,部分核心模块直接加载至内存中。因此,引入这部分核心模块时,可省略文件定位和编译执行两个步骤,且在路径分析中优先于文件模块判断,故其加载速度最快。
核心模块实际上分为由 C/C++ 编写和由 JavaScript 编写两部分:C/C++ 文件存放于 Node.js 项目的 src 目录下,JavaScript 文件存放于 lib 目录下。显然,这两部分模块的编译执行流程存在差异。
JavaScript 核心模块的编译执行
对于 JavaScript 核心模块的编译,在 Node.js 源代码编译过程中,Node.js 会采用 V8 附带的 js2c.py 工具,将所有内置 JavaScript 代码(包括 JavaScript 核心模块)转换为 C++ 数组。JavaScript 代码以字符串形式存储在 node 命名空间中。在启动 Node.js 进程时,JavaScript 代码直接加载至内存。
当引入 JavaScript 核心模块时,Node.js 会调用 process.binding() ,通过模块标识符分析定位其在内存中的位置并取出。取出后,JavaScript 核心模块同样经历模块包装器的包装,然后被执行,导出 exports 对象并返回给调用者。
C/C++ 核心模块的编译执行
在核心模块中,部分模块完全由 C/C++ 编写,部分模块则由 C/C++ 完成核心逻辑,其他部分由 JavaScript 实现封装或向外导出,以满足性能需求。例如 buffer、fs、os 等模块均部分通过 C/C++ 编写。这种 C++ 模块主内完成核心、JavaScript 模块主外实现封装的模式,是 Node.js 提升性能的常见手段。
核心模块中由纯 C/C++ 编写的部分称为内建模块,如 node_fs、node_os 等,它们通常不直接由用户调用,而是被 JavaScript 核心模块直接依赖。
因此,在 Node.js 的核心模块引入过程中,存在如下引用链:

那么,JavaScript 核心模块是如何加载内建模块的?
还记得 process.binding() 方法吗?Node.js 通过调用该方法实现将 JavaScript 核心模块从内存中取出。该方法同样适用于 JavaScript 核心模块,用于协助加载内建模块。
具体到该方法的实现,加载内建模块时,首先创建一个空的 exports 对象,然后调用 get_builtin_module() 方法取出内建模块对象,通过执行 register_func() 填充 exports 对象,最后返回给调用方完成导出。这就是内建模块的加载和执行过程。
通过以上分析,对于引入核心模块这一引用链,以 os 模块为例,大致流程如下:

总结来说,引入 os 模块的过程经历 JavaScript 文件模块的引入、JavaScript 核心模块的加载和执行,以及内建模块的加载执行。该过程繁琐复杂,但对于模块调用者而言,由于屏蔽了底层复杂实现与细节,仅需通过 require() 即可完成整个模块导入,简洁且友好。
总结
本文介绍了文件模块与核心模块的基本概念,以及它们在文件定位、编译或执行等流程中的具体过程和注意事项。
具体而言:
- 文件模块根据文件定位过程的不同,可分为普通文件模块和自定义模块。普通文件模块因路径明确可直接定位,有时涉及文件扩展名分析与目录分析过程;自定义模块则依据模块路径进行查找,查找成功后同样通过目录分析进行最终文件定位。
- 文件模块根据编译执行流程的不同,可分为 JavaScript 模块和 C/C++ 扩展模块。JavaScript 模块经模块封装器包装后,通过
vm模块的runInThisContext方法执行;C/C++ 扩展模块因已是编译生成的可执行文件,可直接执行并返回导出对象给调用方。 - 核心模块分为 JavaScript 核心模块和内建模块。JavaScript 核心模块在 Node.js 进程启动时即被加载至内存中,通过
process.binding()方法可将其取出并执行;内建模块的编译执行需经历process.binding()、get_builtin_module()和register_func()函数的处理。
此外,我们还得出了 Node.js 引入核心模块的引用链:文件模块 --> JavaScript 核心模块 --> 内建模块,并学习了 C++ 模块主内完成核心、JavaScript 模块主外实现封装的模块编写方式。
到此这篇关于一文详解 Node.js 中文件模块与核心模块的文章就介绍到这了,更多相关 Node.js 文件模块与核心模块内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!
全栈开发中JavaScript与Java怎么协作?前后端技术栈配合解析
2026-11-04 11:08:24
Node.js官网访问慢?国内镜像加速下载方案
2026-11-03 18:57:24
Node.js 连接 MySQL 数据库实现增删改查操作
2026-11-03 14:31:14
Windows系统安装Node.js:下载、配置与环境验证
2026-11-03 14:15:56
Linux (Ubuntu/CentOS) 环境下安装Node.js的常用方法
2026-11-03 10:18:39
