const path = require('path');
const fs = require('fs');
const vm = require('vm');
class ZLModule {
constructor(id) {
this.id = id;//保存当前模块的绝对路径
this.exports = {};
}
}
ZLModule._cache = {};//存储已经加载过的module对象
ZLModule._extensions = {
'.json': function(module) {
let json = fs.readFileSync(module.id);
module.exports = JSON.parse(json);
},
'.js': function (module) {
//将js文件包裹起来,包裹成一个方法
let script = fs.readFileSync(module.id);
script = ZLModule.wrapper[0] + '\n' + script + ZLModule.wrapper[1];
//将字符串转化成方法
fn = vm.runInThisContext(script);
fn.call(module.exports,module.exports,require,module);
}
}
ZLModule.wrapper = ['(function(exports, require, module, __filename, __dirname) {','\n})'];
function ZLRequire(filePath) {
//1、转化成绝对路径
let absPath = path.join(__dirname, filePath);
//2、根据路径从缓存中获取模块,如果没有则新建模块对象(新对象需要加载执行);并最终返回模块对象的exports对象。
let cacheModule = ZLModule._cache[absPath];
if(cacheModule) {
return cacheModule.exports;
}else {
let module = new ZLModule(absPath);
ZLModule._cache[absPath] = module;
tryModuleLoad(module);//处理模块,主要是处理module.exports对象
return module.exports;
}
}
function tryModuleLoad(module) {
// 取出模块后缀,根据不同后缀名调用不同的加载方法
let extName = path.extname(module.id);
let fn = ZLModule._extensions[extName];
// ZLModule._extensions[extName](module);
fn(module);
}
const aModule = ZLRequire('a.js');
一个类Module: 用来定义引入文件(模块的实例,含有两个实例属性,id-保存文件绝对路径和exports-保存文件模块导出对象)
Molude类上保存了两个公共属性:_cache对象(用来存储已经加载过的js模块,通过路径识别);_extensions对象(用来保存不同类型文件的处理方法,根据文件路径后缀名区别)
两个方法: require和tryModuleLoad
require: 用来引入文件(文件路径),直接返回已经加载过的module对象的exports对象;或对于新引入文件创建module实例(并调用tryModuleLoad方法处理文件,得到最终的module.exports对象并返回)
tryModuleLoad: 供require调用,用来处理读取进来的不同类型的文件(根据文件路径名的后缀名进行区分)
注意
- 引用模块最终返回的结果,是其模块对象module的属性(对象)— exports,即module.exports;在调用tryModuleLoad方法时,将其作为exports参数的实参传了进去,所以exports拥有其引用;
所以在模块文件中不可以对exports直接赋值(可以给它的属性赋值),因为最终返回的是module.exports。
因为模块中的代码是被包裹起来,然后用call调用的,并在其中改变了this指向,同样指向module.exports,所以里面的this同exports可以有相同的作用。
网友评论