使用
定义暴露模块
module.exports = value;
exports.xxx = value;
// const exports = modules.exports;
引入模块:
var module = require(模块名或模块路径);
模块加载机制
require 某个模块时,会从头执行该模块文件的代码,
module.exports = xxx
也只是其中的一句执行代码,但该结果会缓存到一个全局对象中
// c.js
let c = 1;
c = c + 1;
module.exports = c;
c = 6;
const c = require('./c.js');
console.log(c); // c的值是2
{
"c.js": 2,
}
加载顺序
实现 require
MyModule._load = function (request) { // request是我们传入的路劲参数 const filename = MyModule._resolveFilename(request);
// 先检查缓存,如果缓存存在且已经加载,直接返回缓存 const cachedModule = MyModule._cache[filename]; if (cachedModule !== undefined) { return cachedModule.exports; }
// 如果缓存不存在,我们就加载这个模块 // 加载前先new一个MyModule实例,然后调用实例方法load来加载 // 加载完成直接返回module.exports const module = new MyModule(filename);
// load之前就将这个模块缓存下来,这样如果有循环引用就会拿到这个缓存,但是这个缓存里面的exports可能还没有或者不完整 MyModule._cache[filename] = module;
module.load(filename);
return module.exports; }
```javascript
MyModule._resolveFilename = function (request) {
const filename = path.resolve(request); // 获取传入参数对应的绝对路径
const extname = path.extname(request); // 获取文件后缀名
// 如果没有文件后缀名,尝试添加.js和.json
if (!extname) {
const exts = Object.keys(MyModule._extensions);
for (let i = 0; i < exts.length; i++) {
const currentPath = `${filename}${exts[i]}`;
// 如果拼接后的文件存在,返回拼接的路径
if (fs.existsSync(currentPath)) {
return currentPath;
}
}
}
return filename;
}
编译执行
为了注入 exports, require, module, __dirname, __filename
这几个变量,将原本的代码包裹进一个匿名函数中
MyModule.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
MyModule.wrap = function (script) {
return MyModule.wrapper[0] + script + MyModule.wrapper[1];
};
MyModule.prototype._compile = function (content, filename) {
const wrapper = Module.wrap(content); // 获取包装后函数体
// vm是nodejs的虚拟机沙盒模块,runInThisContext方法可以接受一个字符串并将它转化为一个函数
// 返回值就是转化后的函数,所以compiledWrapper是一个函数
const compiledWrapper = vm.runInThisContext(wrapper, {
filename,
lineOffset: 0,
displayErrors: true,
});
// 准备exports, require, module, __filename, __dirname这几个参数
// exports可以直接用module.exports,即this.exports
// require官方源码中还包装了一层,其实最后调用的还是this.require
// module不用说,就是this了
// __filename直接用传进来的filename参数了
// __dirname需要通过filename获取下
const dirname = path.dirname(filename);
// 第一个 this.exports 说明我们 js 文件里面 this 是对 module.exports 的一个引用
// 第二个 this.exports 说明 js 文件里面的 exports 也是对 module.exports 的一个引用
compiledWrapper.call(this.exports, this.exports, this.require, this,
filename, dirname);
}