使用
定义暴露模块
module.exports = value;exports.xxx = value;// const exports = modules.exports;
引入模块:
var module = require(模块名或模块路径);
模块加载机制
require 某个模块时,会从头执行该模块文件的代码,
module.exports = xxx也只是其中的一句执行代码,但该结果会缓存到一个全局对象中
// c.jslet 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; }
```javascriptMyModule._resolveFilename = function (request) {const filename = path.resolve(request); // 获取传入参数对应的绝对路径const extname = path.extname(request); // 获取文件后缀名// 如果没有文件后缀名,尝试添加.js和.jsonif (!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);}
