禁止转载,侵权必究!
一.commonjs 规范
- 1.每个 js 文件都是一个模块
- 2.每个模块想去引⽤别⼈的模块,需要采⽤ require 语法 => import
- 3.每个模块想被别⼈使⽤需要采⽤ module.exports 进⾏导出 => export
const path = require("path");
const fs = require("fs");
const vm = require("vm");
function Module(id) {
this.id = id;
this.exports = {}; // 存储加载后的结果
}
Module._extensions = {
".js"(module) {
// 读取⽂件内容
const content = fs.readFileSync(module.id, "utf8");
// 给读取到的内容包裹函数,实现隔离
let wrapperFn = vm.compileFunction(content, [
"exports",
"require",
"module",
"__filename",
"__dirname",
]);
let exports = this.exports;
let thisValue = exports;
let require = req;
let filename = module.id;
let dirname = path.dirname(filename);
// 调⽤函数传⼊所需的参数
Reflect.apply(wrapperFn, thisValue, [
exports,
require,
module,
filename,
dirname,
]);
},
};
Module._resolveFilename = function (id) {
// 处理成需要的⽂件名
const exts = Object.keys(Module._extensions);
for (let i = 0; i < exts.length; i++) {
const fileUrl = path.resolve(__dirname, id) + exts[i];
if (fs.existsSync(fileUrl)) return;
fileUrl;
}
throw new Error("模块找不到");
};
Module.prototype.load = function (filename) {
let ext = path.extname(filename);
Module._extensions[ext](this);
};
Module._cache = {};
function req(id) {
// 1.根据传⼊的路径解析出绝对路径
let absPath = Module._resolveFilename(id);
// 2.创建模块
const module = new Module(absPath);
// 3.根据路径加载模块
module.load(absPath);
// 4.将加载后的结果返回
return module.exports;
}
const str = req("./a"); // .js 后缀 .json 后缀, 默认先找js文件
console.log(str);
1.模块的缓存
Module._cache = {};
function req(id) {
// 1.根据传⼊的路径解析出绝对路径
let absPath = Module._resolveFilename(id);
const existsModule = Module._cache[absPath];
if (existsModule) {
return existsModule.exports; // 如果缓存过,则直接返回缓存过的结果
}
// 2.创建模块
const module = new Module(absPath);
Module._cache[absPath] = module;
// 3.根据路径加载模块
module.load(absPath);
// 4.将加载后的结果返回
return module.exports;
}
2.json 模块加载策略
Module._extensions = {
// ...
".json"(module) {
// 读取⽂件内容
const content = fs.readFileSync(module.id, "utf8");
return JSON.parse(content);
},
};
3.循环引⽤问题
// module-a.js
const b = require("./module-b");
console.log(b);
module.exports = "我是a模块";
// module-b.js
const a = require("./module-a");
console.log(a);
module.exports = "我是b模块";
commonjs 不会死循环,⽽是加载已经加载的部分结果,因为如果缓存中有,则使⽤缓存中的 exports 结果。初始化时默认会将当前模块也放⼊到缓存中。
4.exports 和 module.exports 的区别
module.exports 和 exports 引⽤的是同⼀个内存地址,但是导⼊模块时最终采⽤的是 module.exports 结果。
function require() {
let exports = (module.exports = {});
// 这⾥要注意,如果我们直接修改exports是⽆效的
// exports = {}; // ⽆效
// exports.xxx = {}; // 有效
return module.exports;
}
同时我们还要注意 exports 和 module.exports 不能⼀起使⽤,否则使⽤ exports 导出的结果将会失效。
5.this 问题
Node 中的 this 指向的是 exports 对象,不是全局对象。⽬的是为了⽤户可以再 this 上挂载属性实现快速导出
6.思考
如果⽤户导出的是⼀个变量,后续变量被修改了,是否会影响导⼊的值。
// exportsA.js
let a = 0;
setInterval(() => {
a++;
}, 1000);
module.exports = a; // 如果导出的是引⽤类型则会产⽣变化。
// useExportsA.js
setInterval(() => {
let a = require("./exportsA");
console.log(a); // require会缓存上次导⼊的结果。
}, 1000);