const { resolve, extname } = require("path");
const { readFileSync, accessSync } = require("fs");
const { runInThisContext } = require("vm"); //这个可以把字符串函数转换成真正的函数
class Module {
//声明一个Module类,用于模块的数据
static _extname = [".js", ".json", ".node"];
static _cache = new Map(); //用于缓存加载过的模块
static _extension = {
//用于处理不同后缀的文件
".js"(module) {
// 开始读取文件
let fileContext = readFileSync(module.id, "utf8");
// 读取文件后,要执行这串内容
const fun = runInThisContext(`
(function(exports,require,module,filename,dirname){
${fileContext}
})
`);
fun.call(
module.exports,
module.exports,
require,
module,
__filename,
__dirname
);
},
};
exports = {};
constructor(id) {
this.id = id;
}
}
module.exports = function (path) {
let index = 0;
let absPath = resolve(__dirname, path); //拿到绝对路径
let orginPath = absPath; //存储一份原始的绝对路径
const realFilePath = (absPath) => {
if (index > Module._extension.length) {
throw Error("请输入正确的文件路径!!!");
}
try {
// 通过accessSync来判断当前路径的文件是否存在,如果存在将其返回,不存在会抛异常,走catch,递归查找
accessSync(absPath);
return absPath;
} catch {
return realFilePath(orginPath + Module._extname[index++]);
}
};
const handleModule = (module) => {
//处理模块
let path = module.id; //拿到模块的绝对路径
let ext = extname(path); //拿到模块的后缀
Module._extension[ext](module);
};
// 传进来的可能只是./a,没有文件后缀,所以需要自行匹配,拿到正确的路径
let realAbsPath = realFilePath(absPath);
// 判断缓存里面是否有,有就直接返回模块的exports
if (Module._cache.has(realAbsPath)) {
return Module._cache.get(realAbsPath).exports;
}
let module = new Module(realAbsPath);
handleModule(module);
Module._cache.set(realAbsPath, module);
return module.exports;
};
测试方式:新建一个b.js和a.js文件,在b.js中通过require来引入手写的myRequire函数,然后再通过myRequire来引入a.js
let myRequire = require("./myRequire.js");
let teacher = myRequire("./a");
console.log(teacher)
然后再附一道面试题
this.a = 1
exports.b = 2
exports = {c:3}
module.exports = {d:4}
exports.e = 5
module.exports.g = 7
this.f = 6
解析:
通过下面代码可以知道this === exports === module.exports,因为在执行fun.call的时候,传入的都是一个东西,
1.第一行 this = {a:1}
2.第二行 this = {a:1,b:2}
3.第三行因为重新赋值了,所以不在指向this这个对象了,所以 exports = {c:3}
4.第四行也重新赋值了,所以也不在指向this这个对象,所以module.exports = {d:4}
5.第五行 exports = {c:3,e:5}
6.第六行 module.exports = {d:4,g:7}
7.第七行 this = {a:1,b:2,f:6}
因为最终返回的是module.exports,所以结果是{d:4,g:7}
static _extension = {
//用于处理不同后缀的文件
".js"(module) {
// 开始读取文件
let fileContext = readFileSync(module.id, "utf8");
// 读取文件后,要执行这串内容
const fun = runInThisContext(`
(function(exports,require,module,filename,dirname){
${fileContext}
})
`);
fun.call(
module.exports,
module.exports,
require,
module,
__filename,
__dirname
);
},
};