实现简易版本的commonjs加载
带着下面的问题去读读代码吧!
-
node
里面的文件就是模块? - 读取的
js
文件,是如何执行的? -
exports
module.exports
的关系? -
js
里面的对象之间的引用关系?
如何理解文件就是模块?
在node
里面,js
代码在被引入过去都会被包装成如下的函数,是不是我们只需要再我们的require
函数里面去调用这个函数(当然下面的那个函数需要我们自己读取到文件内容,然后组装成下面的函数形式),解析函数字符串为可执行的js,传入引用的参数。module.exports
就会被赋值。再返回module.exports
,就得到了需要的模块里面的内容了
// (function(exports,module,require){
this == module.exports // true
module.exports = 'hello';
// })
module.exports
与 exports
exports = module.exports
exports.a = 'xxx' // 一个引用地址,module.exports也会被赋值
exports = 'xxx' // 这个时候exports的引用地址就错误了。就获取不到模块的内容了
参数的引用
但我们传递给函数的参数是一个引用类型数据的时候。当函数执行的时候,会先执行参数赋值的阶段,引用类型数据的话,赋值的就是一个引用地址。就会出现如下的情况
var test = {}
function fn(a){
a.name = 'xxx'
}
fn(test)
console.log(test) // {name:'xxx'}
代码实现如下
const fs = require("fs");
const path = require("path");
const vm = require("vm");
function Module(filePath) {
this.id = filePath;
this.exports = {};
}
Module._cache = {};
Module.fnStr = ["(function(module,exports,req,__fileName,__dirname){\n", "})"];
Module.extensions = {
".js": function(module) {
const content = fs.readFileSync(module.id, "utf8");
const fnString = Module.fnStr[0] + content + Module.fnStr[1];
const fn = vm.runInThisContext(fnString);
// 文件就是模块 exports = module.exports exports其实就是指向的 module.exports
// 函数执行后 会给传递进去的module.exports赋值。module又是一个对象,函数参数的引用,就给下面实例化的 module上赋值了。就能够获取到文件的内容了
fn.call(
module.exports,
module,
module.exports,
req,
module.id,
path.dirname(module.id)
);
},
".json": function(module) {
const jsonString = fs.readFileSync(module.id, "utf8");
module.exports = JSON.parse(jsonString);
}
};
Module.resolveFileName = function(filePath) {
let absolutePath = path.resolve(__dirname, filePath);
let flag = fs.existsSync(absolutePath); // 判断文件是否存在
let current = absolutePath;
if (!flag) {
let keys = Object.keys(Module.extensions);
for (let i = 0; i < keys.length; i++) {
let combinePath = absolutePath + keys[i];
let flag = fs.existsSync(combinePath); // 增加文件后缀后再判断文件是否存在
if (flag) {
current = combinePath;
break;
} else {
current = null;
}
}
}
if (!current) {
throw new Error(`当前文件不存在${current}`);
}
return current;
};
Module.prototype.load = function() {
// this.id 就是文件路径
let extName = path.extname(this.id);
Module.extensions[extName](this);
};
function req(filePath) {
let absolutePath = Module.resolveFileName(filePath); // 获取到文件的绝对路径 支持了 动态匹配.js .json后缀
let module = new Module(absolutePath);
if (Module._cache[absolutePath]) {
return Module._cache[absolutePath].exports;
}
// 值是一个引用地址。真正有值的时候是再执行完module.load()后
Module._cache[absolutePath] = module;
module.load(); // 读取文件
return module.exports;
}
本人前端菜鸟一枚,欢迎阅读并指出错误!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。