node中的module.exports到底咋实现的?
具体思路:
- 解析出一个绝对路径来
2)根据绝对路径查找文件,没有加后缀的,添加后缀查找文件
-
查到文件情况下,根据文件名查内容。
1.先去缓存中看一下这个文件是否存在,如果有,则返回缓存
2.如果没有,则执行以下操作:
i.创建一个模块 module ii.根据不同的后缀名,加载模块 iii.加载完成后,将内容存到缓存中。
上代码:
let fs = require('fs');
let path = require('path');
let vm = require('vm');
function req(moduleId) {
let p = Module._resolveFileName(moduleId);// p是一个绝对路径
if (Module._cacheModule[p]) { //从缓存中取
// 模块不存在,如果有直接把exports对象返回即可
return Module._cacheModule[p].exports;
}
let module = new Module(p); // 表示没有缓存就生成一个模块
// 加载模块
let content = module.load(p); // 加载模块
Module._cacheModule[p] = module;
module.exports = content; //module.exports = {name:'zfpx'};
return module.exports
}
let a = req('./a.js');
复制代码
具体其余模块讲解:
1.Module._resolveFileName:用于解析绝对路径
Module._resolveFileName = function (moduleId) {
let p =path.join(__dirname,moduleId);//绝对路径
// 没有后缀我在加上后缀 如果传过来的有后缀就不用加了
if (!path.extname(moduleId)) {
let arr = Object.keys(Module._extensions);
for (let i = 0; i < arr.length; i++) {
let file = p + arr[i];
try {
fs.accessSync(file);//判断文件是否存在
return file;
} catch (e) {
console.log(e);
}
}
} else {
return p;
}
}
复制代码
2.Module._cacheModule是一个对象,用来存加载过的模块
key: 文件名,value: 模块名
Module._cacheModule = {}// 根据的是绝对路径进行缓存的
复制代码
3.let module = new Module(p); // 表示没有缓存就生成一个模块 其中p是文件名
function Module(p) {
this.id = p; // 当前模块的标识,Module._cacheModule对象取缓存时,根据id判断缓存是否存在
this.exports = {}; // 每个模块都有一个exports属性
}
复制代码
4.let content = module.load(p); // 加载模块 我们引入的文件可能时.js,.json,.node格式,load方法就是根据不同的后缀名,做不同方式的解析
// 模块加载的方法
Module.prototype.load = function (filepath) {
// 判断加载的文件是json还是node或者是js
let ext = path.extname(filepath); // 判断文件的后缀名是 .js/.json/.node
let content = Module._extensions[ext](this); // content就是json的内容
return content
}
复制代码
5.load方法的核心时Module._extensions,这个方法几乎是核心内容了它的作用是,获得文件内容
// 所有的加载策略
Module.wrapper = ['(function(exports,require,module){','\n})'];
Module._extensions = {
'.js': function (module) {
// 读取js文件 增加一个闭包了
let script = fs.readFileSync(module.id,'utf8');
let fn = Module.wrapper[0] + script + Module.wrapper[1];
let ff = vm.runInThisContext(fn);
//在module.exports环境下执行(function(exports,require,module){module.exports='zfpx'})函数,
// 参数分别对应exports-->module.exports(暗含exports=module.exports),
// require-->req,module
ff.call(module.exports,module.exports,req,module);
return module.exports
},
'.json': function (module) {
return JSON.parse(fs.readFileSync(module.id, 'utf8')); // 读取那个文件
},
}
复制代码
重点来了,解析js方法我怎么看不懂了。不要急。
let fn = Module.wrapper[0] + script + Module.wrapper[1];这句是把文件的内容通过闭包包起来。 let ff = vm.runInThisContext(fn);
vm.runInThisContext()方法会创建一个独立的沙箱环境,以执行对参数fn的编译,运行并返回结果。 vm.runInThisContext()方法运行的代码没有权限访问本地作用域,但是可以访问Global全局对象。
ff.call(module.exports,module.exports,req,module);--->ff是:(function(exports,require,module)) 传入参数,执行闭包,这里解释下exports和module.exports的关系
传参对应关系:
exports---->module.exports
require ----> req
module ----> module
可见:
exports 是module.export 的一个别名; module.exports = exports
比如这里被引入的文件内容:
this === module.exports (true) //this就是module.exports
module.exports = 'zfpx'; // 'zfpx'
复制代码
若将module.exports='zfpx'改为exports='ff';则结果为{}
本来exports = module.exports = {}
后来exports = 'zfpx',切断了exports
和module.exports的关系,而最后返回的是module.exports,因此结果是{}
若将module.exports='zfpx'改为exports.a = '1',则结果为{'a':1}
本来exports = module.exports = {}
后来exports.a = 'zfpx';
则module.exports = {a:'zfpx'}