广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >一文详解Node中的文件模块与核心模块
  • 700
分享到

一文详解Node中的文件模块与核心模块

2024-04-02 19:04:59 700人浏览 独家记忆
摘要

目录前言文件模块普通文件模块的查找自定义模块的查找文件模块的编译执行JSON 文件的编译执行javascript 文件的编译执行C/C++ 扩展模块的编译执行核心模块JavaScri

前言

在我们使用 node 进行日常开发时,经常会使用 require 导入两类模块,一类是我们自己编写的模块或使用 npm 安装的第三方模块,这类模块在 Node 中称为 文件模块;另一类则是 Node 内置的提供给我们使用的模块,如 osfs 等模块,这些模块被称为 核心模块

需要注意的是,文件模块与核心模块的差异不仅仅在于是否被 Node 内置,具体到模块的文件定位、编译和执行过程,两者之间都存在明显的差别。不仅如此,文件模块还可以被细分为普通文件模块、自定义模块或 C/C++ 扩展模块等等,不同的模块在文件定位、编译等流程也存在诸多细节上的不同。

本文会就这些问题,理清文件模块与核心模块的概念以及它们在文件定位、编译或执行等流程的具体过程和需要注意的细节,希望对你有所帮助。

我们先从文件模块讲起。

文件模块

什么是文件模块呢?

在 Node 中,使用 .、.. 或 / 开头的模块标识符(也就是使用相对路径或绝对路径)来 require 的模块,都会被当作文件模块。另外,还有一类特殊的模块,虽然不含有相对路径或绝对路径,也不是核心模块,但是会指向一个包,Node 在定位这类模块时,会用 模块路径 逐个查找该模块,这类模块被称为自定义模块。

因此,文件模块包含两类,一类是带路径的普通文件模块,一类是不带路径的自定义模块。

文件模块在运行时动态加载,需要完整的文件定位、编译执行过程,速度比核心模块慢。

对于文件定位而言,Node 对这两类文件模块的处理有所不同。我们来具体看看这两类文件模块的查找流程。

普通文件模块的查找

对于普通的文件模块,由于携带路径,指向非常明确,查找耗时不会很久,因此查找效率比下文要介绍的自定义模块要高一些。不过还是有两点需要注意。

一是通常情况下,使用 require 引入文件模块时一般都不会指定文件扩展名,比如:

const math = require("math");

由于没有指定扩展名,Node 还不能确定最终的文件。在这种情况下,Node 会按 .js、.json、.node 的顺序补足扩展名,依次尝试,这个过程被称为 文件扩展名分析

另外需要注意的是,在实际开发中,除了 require 一个具体的文件外,我们通常还会指定一个目录,比如:

const axiOS = require("../network");

在这种情况下,Node 会先进行文件扩展名分析,如果没有查找到对应文件,但是得到了一个目录,此时 Node 会将该目录当作一个包来处理。

具体而言,Node 会将目录中的 package.json 的 main 字段所指向的文件作为查找结果返回。如果 main 所指向的文件错误,或者压根不存在 package.json 文件,Node 会使用 index 作为默认文件名,然后依次使用 .js.node 进行扩展名分析,逐个查找目标文件,如果没有找到的话就会抛出错误。

(当然,由于 Node 存在两类模块系统 CJS 和 ESM,除了查找 main 字段外,Node 还会采用其他方式,由于不在本文讨论范围内,就不再赘述了。)

自定义模块的查找

刚才提到,Node 在查找自定义模块的过程中,会使用到模块路径,那什么是模块路径呢?

熟悉模块解析的朋友应该都知道,模块路径是一个由路径组成的数组,具体的值可以看以下这个示例:

// example.js
console.log(module.paths);

打印结果:

可以看到,Node 中的模块存在一个模块路径数组,存放在 module.paths 中,用于规定 Node 如何查找当前模块引用的自定义模块。

具体来讲,Node 会遍历模块路径数组,逐个尝试其中的路径,查找该路径对应的 node_modules 目录中是否有指定的自定义模块,如果没有就向上逐级递归,一直到根目录下的 node_modules 目录,直到找到目标模块为止,如果找不到的话就会抛出错误。

可以看出,逐级向上递归查找 node_modules 目录是 Node 查找自定义模块的策略,而模块路径便是这个策略的具体实现。

同时我们也得出一个结论,在查找自定义模块时,层级越深,相应的查找耗时就会越多。因此相比于核心模块和普通的文件模块,自定义模块的加载速度是最慢的。

当然,根据模块路径查找到的仅仅是一个目录,并不是一个具体的文件,在查找到目录后,同样地,Node 会根据上文所描述的包处理流程进行查找,具体过程不再赘述了。

以上是普通文件模块和自定义模块的文件定位的流程和需要注意的细节,接下来我们来看者两类模块是如何编译执行的。

文件模块的编译执行

当定位到 require 所指向的文件后,通常模块标识符都不带有扩展名,根据上文提到的文件扩展名分析我们可以知道,Node 支持三种扩展名文件的编译执行:

  • JavaScript 文件。通过 fs 模块同步读取文件后编译执行。除了 .node 和 .json 文件,其他文件都会被当作 .js 文件载入。
  • .node 文件,这是用 C/C++ 编写后编译生成的扩展文件,Node 通过 process.dlopen() 方法加载该文件。
  • json 文件,通过 fs 模块同步读取文件后,使用 JSON.parse() 解析并返回结果。

在对文件模块进行编译执行之前,Node 会使用如下所示的模块封装器对其进行包装:

(function(exports, require, module, __filename, __dirname) {
    // 模块代码
});

可以看到,通过模块封装器,Node 将模块包装进函数作用域中,与其他作用域隔离,避免变量的命名冲突、污染全局作用域等问题,同时,通过传入 exports、require 参数,使该模块具备应有的导入与导出能力。这便是 Node 对模块的实现。

了解了模块封装器后,我们先来看 json 文件的编译执行流程。

json 文件的编译执行

json 文件的编译执行是最简单的。在通过 fs 模块同步读取 JSON 文件的内容后,Node 会使用 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 核心模块中无法覆盖的功能或者有特定的性能需求,用户可以编写 C/C++ 扩展模块来达到目的。

那 .node 文件又是什么呢,它跟 C/C++ 扩展模块有什么关系?

事实上,编写好之后的 C/C++ 扩展模块经过编译之后就生成了 .node 文件。也就是说,作为模块的使用者,我们并不直接引入 C/C++ 扩展模块的源代码,而是引入 C/C++ 扩展模块经过编译之后的二进制文件。因此,.node 文件并不需要编译,Node 在查找到 .node 文件后,只需加载和执行该文件即可。在执行的过程中,模块的 exports 对象被填充,然后返回给调用者。

值得注意的是,C/C++ 扩展模块编译生成的 .node 文件在不同平台下有不同的形式:在 *nix 系统下C/C++ 扩展模块被 g++/GCc 等编译器编译为动态链接共享对象文件,扩展名为 .so;在 windows 下则被 Visual C++ 编译器编译为动态链接库文件,扩展名为 .dll。但是在我们实际使用时使用的扩展名却是 .node,事实上 .node 的扩展名只是为了看起来更自然一点,实际上,在 Windows 下它是一个 .dll 文件,在 *nix 下则是一个 .so 文件。

Node 在查找到要 require 的 .node 文件之后,会调用 process.dlopen() 方法对该文件进行加载和执行。由于 .node 文件在不同平台下是不同的文件形式,为了实现跨平台,dlopen() 方法在 Windows 和 *nix 平台下分别有不同的实现,然后通过 libuv 兼容层进行封装。

下图是 C/C++ 扩展模块在不同平台下编译和加载的过程:

核心模块

核心模块在 Node 源代码的编译过程中,就编译进了二进制执行文件。在 Node 进程启动时,部分核心模块就被直接加载进内存中,所以这部分核心模块引入时,文件定位和编译执行这两个步骤可以省略掉,并且在路径分析中会比文件模块优先判断,所以它的加载速度是最快的。

核心模块其实分为 C/C++ 编写的和 JavaScript 编写的两部分,其中 C/C++ 文件存放在 Node 项目的 src 目录下,JavaScript 文件存放在 lib 目录下。显然,这两部分模块的编译执行流程都有所不同。

JavaScript 核心模块的编译执行

对于 JavaScript 核心模块的编译,在 Node 源代码的编译过程中,Node 会采用 V8 附带的 js2c.py 工具,将所有内置的 JavaScript 代码,包括 JavaScript 核心模块,转换为 C++ 里的数组,JavaScript 代码就这样以字符串的形式存储在 node 命名空间中。在启动 Node 进程时,JavaScript 代码就会直接加载进内存。

当引入 JavaScript 核心模块时,Node 会调用 process.binding() 通过模块标识符分析定位到其在内存中的位置,将其取出。在取出后,JavaScript 核心模块同样会经历模块包装器的包装,然后被执行,导出 exports 对象,返回给调用者。

C/C++ 核心模块的编译执行

在核心模块中,有些模块全部由 C/C++ 编写,有些模块则由 C/C++ 完成核心部分,其他部分则由 JavaScript 实现包装或向外导出,以满足性能需求,像 bufferfsos 等模块都是部分通过 C/C++ 编写的。这种 C++ 模块主内完成核心,JavaScript 模块主外实现封装的模式是 Node 提高性能的常见方式。

核心模块中由纯 C/C++ 编写的部分称为内建模块,如 node_fsnode_os 等,它们通常不被用户直接调用,而是被 JavaScript 核心模块直接依赖。

因此,在 Node 的核心模块的引入过程中,存在这样一条引用链:

那 JavaScript 核心模块是如何加载内建模块的呢?

还记得 process.binding() 方法吗,Node 通过调用该方法实现将 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 进程启动时便被加载进内存中,通过 process.binding() 方法可将其取出,然后执行;内建模块的编译执行会经历 process.binding()get_builtin_module() 和 register_func() 函数的处理。

除此之外,我们还得出了 Node 引入核心模块的引用链,即文件模块-->JavaScript 核心模块-->内建模块,也学习了 C++ 模块主内完成核心,JavaScript 模块主外实现封装的模块编写方式。

到此这篇关于一文详解Node 中文件模块与核心模块的文章就介绍到这了,更多相关Node 文件模块与核心模块内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 一文详解Node中的文件模块与核心模块

本文链接: https://www.lsjlt.com/news/164814.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 一文详解Node中的文件模块与核心模块
    目录前言文件模块普通文件模块的查找自定义模块的查找文件模块的编译执行json 文件的编译执行JavaScript 文件的编译执行C/C++ 扩展模块的编译执行核心模块JavaScri...
    99+
    2022-11-13
  • Node中的文件模块和核心模块是什么
    本篇内容介绍了“Node中的文件模块和核心模块是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!文件模块...
    99+
    2022-10-19
  • 一文详解Node中的模块化、文件系统与环境变量
    本篇文章带大家深入了解Node中的模块化、文件系统与环境变量,有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。一、Node.js模块化1.0、变量作用域(1)、在浏览器端使用var或不使用关键字定义的变量属于全局作用域,也就...
    99+
    2023-05-14
    node 模块化 文件系统
  • 一文详解Node中的Express和路由模块
    本篇文章带大家一起学习Node,深入介绍一下Express和路由模块的使用方法,希望对大家有所帮助!ExpressExpress是基于Node.js平台,快速、开放、极简的Web开发框架。Express的作用和Node.js内置的http模...
    99+
    2023-05-14
    node nodejs Express 路由
  • 一文聊聊Node中的net模块
    而客户端和服务端的传输流如下如果角色变成发送者和接受者的时候,传输流如下图:可以看出来传输的过程中,从发送端开始,没经过一层协议都会加上所需要的首部信息.层层把关,层层加码. 然后到了接收端的时候, 就反而行之, 每经过一层都剥去对应的首部...
    99+
    2023-05-14
    net模块 node Node.js
  • 一文聊聊node中的path模块
    path 模块是 nodejs 中用于处理文件/目录路径的一个内置模块,可以看作是一个工具箱,提供诸多方法供我们使用,当然都是和路径处理有关的。同时在前端开发中 path 模块出现的频率也是比较高的,比如配置 webpack 的时候等。本文...
    99+
    2023-05-14
    path模块 Node.js
  • 一文聊聊Node中的fs文件模块和path路径模块(案例分析)
    fs.readFile(path, [options], callback)示例1:读取 demo.txt 文件demo.txt 文件'前端杂货铺'app.js 文件// 导入 fs 文件系统模块 const fs = re...
    99+
    2022-11-22
    node nodejs​ Node.js
  • 详解nodejs 文本操作模块-fs模块(一)
    JS的安全性问题,就决定了JS想要取操作数据库操作文件是不可实现的,而Nodejs作为服务端的JS,如果依然不能操作文件,那么又如何称之为服务端语言呢,所以在Nodejs中,提供了一个fs(File Sys...
    99+
    2022-06-04
    模块 详解 文本
  • node里的filesystem模块文件读写操作详解
    目录一、是什么二、文件知识权限位 mode标识位文件描述为 fd三、方法文件读取fs.readFileSyncfs.readFile文件写入writeFileSyncwriteFil...
    99+
    2022-11-13
  • 一文详解es6中的模块化
    Es Module 的解析流程在开始之前,我们先大概了解一下整个流程大概是怎么样的,先有一个大概的了解:阶段一:构建(Construction),根据地址查找 js 文件,通过网络下载,并且解析模块文件为 Module Record;阶段二...
    99+
    2022-11-22
    ES6 前端 JavaScript 面试
  • Node.js中HTTP模块与事件模块详解
    Node.js的http服务器 通过使用HTTP模块的低级API,Node.js允许我们创建服务器和客户端。刚开始学node的时候,我们都会遇到如下代码: var http = require('http...
    99+
    2022-06-04
    模块 详解 事件
  • 一文带你了解node中的的模块系统
    本篇文章带大家进行node学习,深入浅出的了解node中的的模块系统,希望对大家有所帮助!两年前写过一篇文章介绍模块系统:理解前端模块概念:CommonJs与ES6Module。这篇文章的知识面都是针对刚入门的,比较浅显。在这也纠正文章的几...
    99+
    2023-05-14
    javascript Node.js node
  • Node中的fs文件模块和path路径模块怎么使用
    这篇“Node中的fs文件模块和path路径模块怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Node中的fs文件模...
    99+
    2023-07-04
  • 一文详解Python中logging模块的用法
    目录一、低配logging1.v12.v23.v3二、高配logging1.配置日志文件2.使用日志三、Django日志配置文件一、低配logging 日志总共分为以下五个级别,这个...
    99+
    2023-02-28
    Python logging模块使用 Python logging模块 Python logging
  • 一文详解nodejs的path模块使用
    目录前言APIbasename (获取路径基础名)dirname (获取路径目录名)extname (获取路径扩展名)parse (解析路径)format (序列化路径)isAbso...
    99+
    2022-11-16
    nodejs path模块使用 nodejs path
  • 独立使用umi的核心插件模块示例详解
    目录引言实践结语引言 今天我们做一个有趣的尝试,将 umi 的核心插件模块独立出来作为另一个框架的基础架构,这里我们将它称为 konos。 介于 umi 自身的源码的独立拆分,要实...
    99+
    2023-01-12
    umi 核心插件模块 umi 插件
  • node.js中fs核心模块读写文件操作的示例分析
    这篇文章将为大家详细讲解有关node.js中fs核心模块读写文件操作的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。node.js 里fs模块常用的功能实现文件的...
    99+
    2022-10-19
  • 基于node.js的fs核心模块读写文件操作(实例讲解)
    node.js 里fs模块 常用的功能 实现文件的读写 目录的操作 - 同步和异步共存 ,有异步不用同步 - fs.readFile 都不能读取比运行内存大的文件,如果文件偏大也不会使用readFile方...
    99+
    2022-06-04
    实例 模块 核心
  • Node的事件处理和readline模块详解
    目录一、Node的事件处理二、通过Node的readline模块实现终端的输入总结一、Node的事件处理 1、采用事件驱动模型 2、Node是单线程的,采用事件轮询方式来处理事件 3...
    99+
    2022-11-13
  • Python中的pandas表格模块、文件模块和数据库模块
    目录一、Series数据结构1、Series的创建2、Series属性2、Series缺失数据处理二、DataFrame数据结构1、DataFrame的创建2、DataFrame属性...
    99+
    2022-11-11
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作