使用
// 导出模块, 可以导出任意的数据类型module.exports = "hello world"// 或者使用exports.a = "test"// 引入模块const index = require('./index');
- 一个模块真正导出的是 
module.exports的值,exports只是module.exports的一个引用。通过exports.XXX来修改module.exports.XXX```javascript // 默认 module.exports 的值为{},exports 也指向这个空对象 
// 同时存在 module.exports,再改变对象的指向,exports就没有意义了
// 只能使用 exports.xxx 导出才有效 exports.a = ‘hello world’;
// 引用修改了,导出无效 exports = “hello world”
// 源码在传入函数参数时:exports = module.exports
- require 命令用于加载模块文件。**require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错**。- require 值会缓存,导入过则会通过深拷贝的方式存在内存中。- requrie 运行时加载是整个模块(对象)<a name="TFiXg"></a># 原理实现使用fs、vm、path内置模块,以函数包裹形式实现,把字符串变成可执行函数。```javascriptconst vm = require("vm")const path = require("path")const fs = require("fs")/*** commonjs的require函数:引入module* @param {string} filename module的名称*/function customRequire(filename){const pathToFile = path.resolve(__dirname, filename)const content = fs.readFileSync(pathToFile, 'utf-8')// 使用函数包裹模块,执行函数// 注入形参(全局dioxide):require、module、exports、__dirname、__filenameconst wrapper = ['(function(require, module, exports, __dirname, __filename){','})']const wrappedContent = wrapper[0] + content + wrapper[1]const script = new vm.Script(wrappedContent, {filename: 'index.js'})const module = {exports: {}}//转换为函数,类似eval,(funcion(require, module, exports){ xxx })const result = script.runInThisContext();// 函数执行,引入模块,若内部有require继续递归// exports为module.exports的引用result(customRequire, module, module.exports);return module.exports}global.customRequire = customRequire
总结:
- 读取文件为字符串,包裹为一个函数字符串
 - 放到 vm 里去执行变为函数(类似new Function)
 - 执行函数,传入 require,module,exports 等参数
 
源码
路径:/lib/internal/modules/cjs/
//require函数定义1、Module.prototype.require:调用__load函数2、Module.__load:_cache处理,调用load函数3、Module.prototype.load函数:调用Module._extensions[extension](this, filename);//不同的后缀通过定义不同的函数指定解析规则:以Module._extensions['.js']为例4、Module._extensions['.js'] = function(module, filename) {//读取缓存或者通过readFileSync读取内容if (cached?.source) {content = cached.source;cached.source = undefined;} else {content = fs.readFileSync(filename, 'utf8');}//...//调用compile解析module._compile(content, filename);}5、Module.prototype._compile = function(content, filename){//生成包裹函数:warpSafe获取函数字符串并使用vm执行生成执行函数const compiledWrapper = wrapSafe(filename, content, this);//执行函数const exports = this.exports;const thisValue = exports;const module = this;if (inspectorWrapper) {result = inspectorWrapper(compiledWrapper, thisValue, exports,require, module, filename, dirname);} else {//静态方法Reflect.apply(target, thisArgument, argumentsList)//通过指定的参数列表发起对目标(target)函数的调用result = ReflectApply(compiledWrapper, thisValue,[exports, require, module, filename, dirname]);}return result;}6、function wrapSafe(filename, content, cjsModuleInstance) {/* 生成包裹函数字符:let wrap = function(script) {return Module.wrapper[0] + script + Module.wrapper[1];};const wrapper = ['(function (exports, require, module, __filename, __dirname) { ','\n});',];*/const wrapper = Module.wrap(content);//获取包裹函数return vm.runInThisContext(wrapper, {filename,lineOffset: 0,displayErrors: true,importModuleDynamically: async (specifier) => {const loader = asyncESM.ESMLoader;return loader.import(specifier, normalizeReferrerURL(filename));},});}
- 后缀名扩展解析,策略模式 ```javascript const Module = require(‘module’) // 后缀解析扩展:.test后缀同.js后缀 Module._extensions[‘.test’] = Module._extensions[‘.js’]
 
// 切面编程,覆盖 js 的处理策略 const prevFunc = Module._extensions[‘.js’] Module._extensions[‘.js’] = function(…args){ console.log(‘load script’) prevFunc.apply(prevFunc, args) } ```
