关键词:
- 社区标准
- 使用函数实现
- 仅node环境支持
- 动态依赖(需要代码运行后才能确定依赖)
- 动态依赖是同步执行的
再实现require引入之前我们先了解一个问题
问题:字符串如何能变成js执行
1.eval()函数会将传入的字符串当做 JavaScript 代码进行执行
console.log(eval('2 + 2'));
// Expected output: 4
console.log(eval(new String('2 + 2')));
// Expected output: 2 + 2
console.log(eval('2 + 2') === eval('4'));
// Expected output: true
2.new Fuction ‘模板引擎的实现原理’ 可以获取全局变量,还是会有污染的信息
const sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 6));
// Expected output: 8
3.node中自己实现了一个模块vm,不受环境(沙箱环境)
var a = 1
const vm = require('vm');
// 在node中全局变量是在多个模块下共享的, 所以不要通过global来定义属性
let fn = vm.runInThisContext(`(function(a){console.log(a)})`);
fn(a)
// Expected output: 1
require的实现
- require方法 -> Module.protoype.require方法
- Module._load 加载模块
- Module._resolveFilename 方法就是把路径变成了绝对路径 添加后缀名 (.js .json) .node
- new Module 拿到绝对路径创造一个模块 this.id exports = {}
- module.load 对模块进行加载
- 根据文件后缀 Module._extensions['.js'] 去做策略加载
- 用的是同步读取文件
- 增加一个函数的壳子 并且让函数执行 让 module.exports 作为了this
- 用户会默认拿到module.exports的返回结果
最终返回的是 exports对象
1.1 先写一个req函数
const fs = require('fs');
const path = require('path');
const vm = require('vm');
function req(filename){
filename = Module._resolveFilename(filename); // 1.创造一个绝对引用地址,方便后续读取
const module = new Module(filename); // 2.根据路径创造一个模块
module.load(); // 就是让用户给module.exports 赋值
return module.exports; // 默认是空对象
}
1.2 创建Module函数
function Module(id){
this.id = id;
this.exports = {}
}
1.3 _resolveFilename函数,获取文件的绝对路径
Module._resolveFilename = function (id) {
let filePath = path.resolve(__dirname,id)
let isExists = fs.existsSync(filePath);
if(isExists) return filePath;
// 尝试添加后缀
let keys = Object.keys(Module._extensions); // 以后Object的新出的方法 都会放到Reflect上
for(let i =0; i < keys.length;i++){
let newPath = filePath + keys[i];
if(fs.existsSync(newPath)) return newPath
}
throw new Error('module not found')
}
1.4 加载load函数,就是让用户给module.exports赋值
Module.prototype.load = function (){
let ext = path.extname(this.id); // 获取文件后缀名
Module._extensions[ext](this);
}
1.5 根据文件的后缀名,去做策略加载,利用node中的vm模块,加载对应的模块,给module.exports赋值
Module._extensions = {
'.js'(module){
let script = fs.readFileSync(module.id,'utf8');
let templateFn = `(function(exports,module,require,__dirname,__filename){${script}})`;
let fn = vm.runInThisContext(templateFn);
let exports = module.exports;
let thisValue = exports; // this = module.exports = exports;
let filename = module.id;
let dirname = path.dirname(filename);
// 函数的call 的作用 1.改变this指向 2.让函数指向
fn.call(thisValue,exports,module,req,dirname,filename); // 调用了a模块 module.exports = 100;
},
'.json'(module){
let script = fs.readFileSync(module.id,'utf8');
module.exports = JSON.parse(script)
}
}
1.6 再多次引入文件的时候,缓存文件,防止多次访问,改造req函数
Module._cache = {}
function req(filename){
filename = Module._resolveFilename(filename); // 1.创造一个绝对引用地址,方便后续读取
let cacheModule = Module._cache[filename]
if(cacheModule) return cacheModule.exports; // 直接将上次缓存的模块丢给你就ok了
const module = new Module(filename); // 2.根据路径创造一个模块
Module._cache[filename] = module; // 最终:缓存模块 根据的是文件名来缓存
module.load(); // 就是让用户给module.exports 赋值
return module.exports; // 默认是空对象
}