使用
// 导出模块, 可以导出任意的数据类型
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内置模块,以函数包裹形式实现,把字符串变成可执行函数。
```javascript
const 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、__filename
const 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) } ```