Node.js模块载入与运行原理深度解析

作者:墨池

时间:2026-10-22

浏览:538

本文深入解析Node.js模块的加载与运行原理,涵盖内置模块、原生JS模块及第三方模块的生成与加载流程。重点分析node.cc中binding()函数的优先级逻辑,以及应用启动时从环境准备到module.js执行的全过程,揭示依赖模块加载的核心机制。

文章目录

前言

在使用Node.js开发时,引用第三方模块是常见操作。这些模块既包括Node.js自带的核心模块(如http、net等),也包含发布在npm仓库上的第三方库(如mssql、elasticsearch等)。

本文将聚焦探讨以下三个核心问题:

  1. Node.js模块的具体加载流程。
  2. 应用程序的启动流程。
  3. 应用程序如何加载其依赖的模块。

1. 模块的加载过程

Node.js中的模块大致可分为以下四类:

a) 内置模块(Builtin Module):以C++形式提供的Node.js核心模块。

b) 常量模块(Constant Module):用于定义Node.js常量的模块。

c) 原生JS模块(Native JS Module):以JavaScript形式提供的Node.js模块。

d) 第三方模块(Third-party Module):由第三方开发者提供的模块。

接下来,我们将首先分析内置模块和原生JS模块的生成过程。

前言

原生JS模块的生成过程相对复杂。编译完成后,系统会在/out/release/obj/gen目录下生成一个名为node_natives.h的文件。

该文件由js2c.py脚本生成,其作用是将Node.js源码中lib目录下的所有JavaScript文件转换为ASCII码,并将其存储在相应的数组中。

前言

相比之下,内置C++模块的生成过程较为简单。每个内置C++模块的入口点都会通过宏NODE_MODULE_CONTEXT_AWARE_BUILTIN扩展为一个函数。例如,对于tcp_wrap模块,它会扩展为static void register_tcp_wrap() attribute(constructor)函数。

熟悉GCC编译器的读者知道,被attribute(constructor)修饰的函数会在Node.js的main()函数之前执行。这意味着内置C++模块会在main()函数执行前被加载到modlist_builtin列表中。modlist_builtin是一个指向struct node_module类型的指针,get_builtin_module()函数会遍历该列表以查找所需的模块。

无论是原生JS模块还是内置C++模块,最终都会被编译为可执行文件。然而,两者的提取方式截然不同:JS模块通过process.binding('natives')获取,而C++模块则直接使用get_builtin_module()函数。

在node.cc文件中提供了一个binding()函数,当应用程序通过require()引用其他模块时,该函数会被调用。下面我们将分析这个函数的具体实现:

前言

从代码中可以观察到,该函数主要服务于三类模块:内置模块(builtin)、常量模块(constants)和原生JS模块(native)。

内置模块具有最高优先级,系统会在modlist_builtin中查找。这个过程非常简单,即遍历整个列表以查找同名模块。找到后,先执行模块的注册函数,然后返回其导出的数据。

常量模块的优先级次之,Node.js中的常量定义通过constants模块导出。

原生JS模块的优先级最低。

2. 应用启动的过程

前言

上图展示了一个流程图,描述了以test.js为参数启动Node.js并最终执行的全过程。整个过程可划分为四个步骤:

1. 可执行文件node:作为Node.js入口,在启动过程中主要承担环境准备的职责。

2. src/node.js:启动脚本,负责初始化启动流程。

3. 原生模块(Native Module):为module.js的执行做准备工作。

4. module.js:作为原生模块,负责加载、编译并执行应用程序代码。

应用如何加载依赖模块

前文提到,NativeModule.require()仅负责帮助引用原生JS模块,这对于lib/module.js而言已经足够。

然而,显然一般应用程序不仅需要引用原生JS模块,还需要引用第三方模块。让我们查看module.js中Module.prototype._require()函数的实现细节。

前言

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章