Node 的模块系统是借鉴 CommonJS 的 Modules 规范实现的,因此,下面我们需要先了解 CommonJS 的 Modules 规范。
CommonJS 对模块的定义非常简单,主要分为 模块引用、模块定义和模块标识三个部分。
方法
- require()
对象
- module.exports
方法的参数
- require()
通过 CommonJS 的这套导出和引入机制,用户不必再考虑变量污染的问题。
Node 中的模块分为两类:
核心模块:
文件模块:
Node 对引入过的模块都会进行缓存,Node 缓存的是编译执行之后的对象,require() 方法对相同模块的加载一律采用缓存优先的方式,这是第一优先级。
模块标识符,就是传入
方法的参数,对于不同的模块标识符,查找和定位的方式是不一样的。
- require()
模块标识在 Node 中主要分为以下几类:
或
- .
开头的相对路径,都会被当做文件模块处理,
- ..
方法会将路径转换为真实路径,并以真实的路径作为索引,将编译执行后的结果存放到缓存中
- require()
开始的绝对路径,同上
- /
在了解自定义模块的查找方式之前,需要先知道
这个概念。
- 模块路径
是
- 模块路径
在定位文件模块的具体文件是制定的查找策略,具体表现为一个路径组成的数组。
- Node
的生成规则如下:
- 模块路径
目录
- node_modules
目录
- node_modules
目录
- node_modules
目录
- node_modules
在加载的过程中,Node 会逐个尝试模块路径中的路径,知道找到目标文件为止
Node 在定位好文件之后,还需要做一些事情,包括:扩展名的分析以及目录、包的处理
扩展名的分析:如果标识符不包含扩展名,Node 会按
- .js
- .json
的次序补充扩展名,依次尝试
- .node
目录、包的处理:如果通过标识符没找到对应文件,但是找到了同名的一个目录
文件,从中取出
- package.json
属性指定的文件名对应
- main
的
- package.json
属性指定的文件名错误或者是直接没有
- main
文件,Node 会将
- package.json
作为默认文件名
- index
编译和执行是引入文件模块的最后一个阶段,在 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 文件会被包装成如下的样子:
- (function (exports, require, module, __filename, __dirname){
- // 文件里本来的 js 代码
- })
对象上的任何方法和属性都可以被外部调用到。
- module.exports
调用
- Node
方法进行加载和执行。
- process.dlopen()
Node 利用
模块同步读取
- fs
文件,调用
- .json
方法得到对象然后赋值给
- JSON.parse()
- module.exports
每一个编译成功的模块都会将其文件路径作为索引缓存在
对象上。
- Module._chche
Node 的核心模块在编译成可执行文件的过程中被编译进了二进制文件。核心模块分为 C/C++ 和 JavaScript 编写的两个部分
第一步:转存为 C/C++ 代码
工具将所有内置的 JavaScript 代码转换成 C++ 里的数组
- js2c.py
命名空间中,是不可执行的
- node
第二步:编译 JavaScript 核心模块
与文件模块有区别的地方在于:获取源代码的方式(核心模块是从内存中加载的)和缓存(
)执行结果的位置。
- NativeModule._cache
由纯 C/C++ 编写的部分统称为内建模块,因为他它们通常不被用户直接调用。Node 的 buffer、crypto、evals、fs、os 等模块都是内建模块。
… 这个坑先留着
Node 在启动时,会生成一个全局变量
,并提供
- process
方法来协助加载内建模块。
- Binding()
前面提到的 JavaScript 核心文件被转换为 C/C++ 数组存储后,便是通过
取出放置在
- process.binding('natives')
中的:
- NativeModule._source
- NativeModule._source = process.binding('natives')
来源: