Node.js 的模块系统

Node.js的模块系统是借鉴 CommonJS 的 Modules 规范实现的,因此,下面我们需要先了解 CommonJS 的 Modules 规范,希望对大家学习Node.js有所帮助。

CommonJS 的 Modules 规范

CommonJS 对模块的定义非常简单,主要分为 模块引用、模块定义和模块标识三个部分。

1. 模块引用 - require() 方法

2. 模块定义 - module.exports 对象

3. 模块标识 - 传递给 require() 方法的参数

通过 CommonJS 的这套导出和引入机制,用户不必再考虑变量污染的问题。

Node 的模块实现

Node 中的模块分为两类:

1. Node 提供的模块 - 核心模块

2. 用户编写的模块 - 文件模块

核心模块:

1. 在 Node 源代码的编译过程中,编译进了二进制文件

2. 在 Node 进程启动时,部分核心模块被直接加载近内存

文件模块:

1. 运行时动态加载,需要完整的路径分析、文件定位、编译执行过程

Node 缓存

Node 对引入过的模块都会进行缓存,Node 缓存的是编译执行之后的对象,require() 方法对相同模块的加载一律采用缓存优先的方式,这是第一优先级。

路径分析和文件定位

模块标识符分析

模块标识符,就是传入 require() 方法的参数,对于不同的模块标识符,查找和定位的方式是不一样的。

模块标识在 Node 中主要分为以下几类:

· 核心模块(http、fs、path .etc),加载优先级仅次于缓存加载,其加载过程最快

· . 或 .. 开头的相对路径,都会被当做文件模块处理, require() 方法会将路径转换为真实路径,并以真实的路径作为索引,将编译执行后的结果存放到缓存中

· / 开始的绝对路径,同上

· 非路径开始的文件模块,它不是核心模块,但是又不写成路径的形式,这类的查找是最费时的,详细的分析在下文

模块路径

在了解自定义模块的查找方式之前,需要先知道 模块路径 这个概念。

模块路径 是 Node 在定位文件模块的具体文件是制定的查找策略,具体表现为一个路径组成的数组。

模块路径 的生成规则如下:

· 当前目录下的 node_modules 目录

· 父目录下的 node_modules 目录

· 父目录的父目录下的 node_modules 目录

· 一直到系统的根目录下的 node_modules 目录

在加载的过程中,Node 会逐个尝试模块路径中的路径,知道找到目标文件为止

文件定位

Node 在定位好文件之后,还需要做一些事情,包括:扩展名的分析以及目录、包的处理

扩展名的分析:如果标识符不包含扩展名,Node 会按 .js .json .node 的次序补充扩展名,依次尝试

目录、包的处理:如果通过标识符没找到对应文件,但是找到了同名的一个目录

1. 首先,Node 会在该目录下查找 package.json 文件,从中取出 main 属性指定的文件名对应

2. 如果 package.json 的 main 属性指定的文件名错误或者是直接没有 package.json 文件,Node 会将 index 作为默认文件名

3. 如果在目录分析的过程中没有定位成功任何的文件,则自定义模块进入下一个模块路径进行搜索,如果路径数组已经遍历完了还没找到目标文件则会抛出一个异常

模块编译

编译和执行是引入文件模块的最后一个阶段,在 Node 中,每个文件都是一个模块,定义如下

function Module(id, parent){

this.id = id

this.exports = {}

this.parent = parent

if (parent && parent.children) {

parent,children.push(this)

}

this.filename = null

this.loaded = false

this.children = []

}

定位到具体的文件后,Node 会新建一个模块对象,然后根据载入路径载入并编译。

载入

不同文件的载入方法是不同的:

· .js 文件:通过 fs 模块同步读取模块后编译执行

· .node 文件:通过 dlopen() 方法加载最后编译生成的文件

· .json 文件:通过 fs 模块同步读取文件后,通过 JSON.parse() 解析返回结果

· 其余扩展名文件

Module._extensions 会被赋值给 require() 的 extensions 属性,如果想对自定义的扩展名进行特殊的加载,可以通过类似 require.extension[’.coffee’] 扩展的方式来实现。

编译

JavaScript 模块的编译

一个正常的 JavaScript 文件会被包装成如下的样子:

(function (exports, require, module, __filename, __dirname){

// 文件里本来的 js 代码

})

module.exports 对象上的任何方法和属性都可以被外部调用到。

C/C++ 模块的编译

Node 调用

process.dlopen() 方法进行加载和执行。

JSON 文件的编译

Node 利用 fs 模块同步读取 .json 文件,调用 JSON.parse() 方法得到对象然后赋值给 module.exports

每一个编译成功的模块都会将其文件路径作为索引缓存在 Module._chche 对象上。

核心模块

Node 的核心模块在编译成可执行文件的过程中被编译进了二进制文件。核心模块分为 C/C++ 和 JavaScript 编写的两个部分

JavaScript 核心模块的编译过程

第一步:转存为 C/C++ 代码

1. Node 采用 V8 附带的 js2c.py 工具将所有内置的 JavaScript 代码转换成 C++ 里的数组

2. 在这个过程中,JavaScript 代码以字符串的形式存储在 node 命名空间中,是不可执行的

3. 在启动 Node 进程时,JavaScript 代码直接加载进内存中

4. 在加载的过程中,JavaScript 核心模块经历标识符分析后直接定位到内存中

第二步:编译 JavaScript 核心模块

与文件模块有区别的地方在于:获取源代码的方式(核心模块是从内存中加载的)和缓存( NativeModule._cache )执行结果的位置。

C/C++ 核心模块的编译过程

由纯 C/C++ 编写的部分统称为内建模块,因为他它们通常不被用户直接调用。Node 的 buffer、crypto、evals、fs、os 等模块都是内建模块。

… 这个坑先留着

内建模块的导出

Node 在启动时,会生成一个全局变量 process ,并提供 Binding() 方法来协助加载内建模块。

前面提到的 JavaScript 核心文件被转换为 C/C++ 数组存储后,便是通过 process.binding(’natives’) 取出放置在 NativeModule._source 中的:

NativeModule._source = process.binding(’natives’)

来源:Mertens Notes

时间: 2024-12-25 10:08:59

Node.js 的模块系统的相关文章

【node.js】模块系统、函数

为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统. 一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码.JSON 或者编译过的C/C++ 扩展. 创建模块 在 Node.js 中,创建一个模块非常简单,如下我们创建一个 'hello.js' 文件,代码如下: var hello = require('./hello'); hello.world(); 以上实例中,代码 require('./hello') 引入了当前目录下的hello.

Node,js的模块系统

模块系统 为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统. 模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的.换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码.JSON 或者编译过的C/C++ 扩展. 创建模块 在 Node.js 中,创建一个模块非常简单,如下我们创建一个 'mk.js' 文件,代码如下: 以上实例中,代码 require('./hello') 引入了当前目录下的hello.js文件(.

Node.js:模块系统

为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统. 模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的.换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码.JSON 或者编译过的C/C++ 扩展. 一.创建模块 在 Node.js 中,创建一个模块非常简单,如下我们创建一个 main.js 文件,代码如下: var hello = require('./hello'); hello.world(); 以上实例中

node.js 16 模块系统

本文参考原文-http://bjbsair.com/2020-03-22/tech-info/2817/ const?http?=?require('http') 在node.js中,一个应用程序由若干模块组成,而这些模块,我们可以理解为是一个JavaScript文件,以.js结尾的文件. 原生模块 对于上述的'http'是原生模块,在node.js安装后是系统自带的.使用模块时只需要通过 require 进行引入即可. 在这段代码中,使用require函数来引用http模块,该函数返回http

Node.js之模块机制

文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. 其实在JavaScript的发展中,它主要是在浏览器前端中被应用广泛.因为在实际应用中, JavaScript的表现能力主要取决于宿主环境的API支持程度, 在最早期,只有对BOM, DOM的支持,随着HTML5的出现,在浏览器中出现了更多,更强大的API供JavaScript调用,但是这些都是发生在前端,后端JavaScript的规范却远远落后.Java有class文件,Phthon有import机制,P

Node.js(二)——模块与包管理工具

http,process等等这些都是模块 一.Node.js的模块与Commonjs规范 1.js的天生缺陷--缺少模块化管理机制 ·表现--JS中容易出现变量被覆盖,方法被替代的情况(既被污染).特别是存在依赖关系时,容易出现错误.这是因为JS缺少模块管理机制,来隔离实现各种不同功能的JS判断,避免它们相互污染. ·解决--经常采用命名空间的方式,把变量和函数限制在某个特定的作用域内,人肉约定一套命名规范来限制代码,保证代码安全运行.jQuery中有许多变量和方法,但是无法直接访问,必须通过j

Node.js文件模块fs监视文件变化

Node.js文件模块fs监视文件变化 Node中文件模块fs监视文件的函数源码如下: fs.watch = function(filename) { nullCheck(filename); var watcher; var options; var listener; if (util.isObject(arguments[1])) { options = arguments[1]; listener = arguments[2]; } else { options = {}; listen

node.js基础模块http、网页分析工具cherrio实现爬虫

node.js基础模块http.网页分析工具cherrio实现爬虫 一.前言      说是爬虫初探,其实并没有用到爬虫相关第三方类库,主要用了node.js基础模块http.网页分析工具cherrio. 使用http直接获取url路径对应网页资源,然后使用cherrio分析. 这里我主要学习过的案例自己敲了一遍,加深理解.在coding的过程中,我第一次把jq获取后的对象直接用forEach遍历,直接报错,是因为jq没有对应的这个方法,只有js数组可以调用. 二.知识点    ①:supera

(译)Node.js的模块-exports和module.exports

原文标题:Node.js Module – exports vs module.exports原文链接:http://www.hacksparrow.com/node-js-exports-vs-module-exports.html exports 和 module.exports 有什么区别? 你一定很熟悉 Node.js 模块中的用来在你的模块中创建函数的 exports 对象,就像下面这样. 创建一个叫做rocker.js的文件: exports.name = function() {