深入理解Commonjs规范及Node模块实现

2020-06-17 06:46:25易采站长站整理

【global】

最容易想到的方法,把一个模块定义的变量复制到全局环境global中,然后另一个模块访问全局环境即可


//a.js
var a = 100;
global.a = a;

//b.js
require('./a');
console.log(global.a);//100

这种方法虽然简单,但由于会污染全局环境,不推荐使用

【module】

而常用的方法是使用nodejs提供的模块对象Module,该对象保存了当前模块相关的一些信息


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 = [];
}

module.id 模块的识别符,通常是带有绝对路径的模块文件名。
module.filename 模块的文件名,带有绝对路径。
module.loaded 返回一个布尔值,表示模块是否已经完成加载。
module.parent 返回一个对象,表示调用该模块的模块。
module.children 返回一个数组,表示该模块要用到的其他模块。
module.exports 表示模块对外输出的值。

【exports】

module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量


//a.js
var a = 100;
module.exports.a = a;

//b.js
var result = require('./a');
console.log(result);//'{ a: 100 }'

为了方便,Node为每个模块提供一个exports变量,指向module.exports。造成的结果是,在对外输出模块接口时,可以向exports对象添加方法


console.log(module.exports === exports);//true

[注意]不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系

模块编译

编译和执行是模块实现的最后一个阶段。定位到具体的文件后,Node会新建一个模块对象,然后根据路径载入并编译。对于不同的文件扩展名,其载入方法也有所不同,具体如下所示

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

node文件——这是用C/C++编写的扩展文件,通过dlopen()方法加载最后编译生成的文件

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

其余扩展名文件——它们都被当做.js文件载入

每一个编译成功的模块都会将其文件路径作为索引缓存在Module._cache对象上,以提高二次引入的性能

根据不同的文件扩展名,Node会调用不同的读取方式,如.json文件的调用如下: