使用

  1. // 导出模块, 可以导出任意的数据类型
  2. module.exports = "hello world"
  3. // 或者使用
  4. exports.a = "test"
  5. // 引入模块
  6. 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

  1. - require 命令用于加载模块文件。**require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错**。
  2. - require 值会缓存,导入过则会通过深拷贝的方式存在内存中。
  3. - requrie 运行时加载是整个模块(对象)
  4. <a name="TFiXg"></a>
  5. # 原理实现
  6. 使用fsvmpath内置模块,以函数包裹形式实现,把字符串变成可执行函数。
  7. ```javascript
  8. const vm = require("vm")
  9. const path = require("path")
  10. const fs = require("fs")
  11. /**
  12. * commonjs的require函数:引入module
  13. * @param {string} filename module的名称
  14. */
  15. function customRequire(filename){
  16. const pathToFile = path.resolve(__dirname, filename)
  17. const content = fs.readFileSync(pathToFile, 'utf-8')
  18. // 使用函数包裹模块,执行函数
  19. // 注入形参(全局dioxide):require、module、exports、__dirname、__filename
  20. const wrapper = [
  21. '(function(require, module, exports, __dirname, __filename){',
  22. '})'
  23. ]
  24. const wrappedContent = wrapper[0] + content + wrapper[1]
  25. const script = new vm.Script(wrappedContent, {
  26. filename: 'index.js'
  27. })
  28. const module = {
  29. exports: {}
  30. }
  31. //转换为函数,类似eval,(funcion(require, module, exports){ xxx })
  32. const result = script.runInThisContext();
  33. // 函数执行,引入模块,若内部有require继续递归
  34. // exports为module.exports的引用
  35. result(customRequire, module, module.exports);
  36. return module.exports
  37. }
  38. global.customRequire = customRequire

总结:

  • 读取文件为字符串,包裹为一个函数字符串
  • 放到 vm 里去执行变为函数(类似new Function)
  • 执行函数,传入 require,module,exports 等参数

源码

路径:/lib/internal/modules/cjs/

  1. //require函数定义
  2. 1Module.prototype.require:调用__load函数
  3. 2Module.__load_cache处理,调用load函数
  4. 3Module.prototype.load函数:调用Module._extensions[extension](this, filename);
  5. //不同的后缀通过定义不同的函数指定解析规则:以Module._extensions['.js']为例
  6. 4Module._extensions['.js'] = function(module, filename) {
  7. //读取缓存或者通过readFileSync读取内容
  8. if (cached?.source) {
  9. content = cached.source;
  10. cached.source = undefined;
  11. } else {
  12. content = fs.readFileSync(filename, 'utf8');
  13. }
  14. //...
  15. //调用compile解析
  16. module._compile(content, filename);
  17. }
  18. 5Module.prototype._compile = function(content, filename){
  19. //生成包裹函数:warpSafe获取函数字符串并使用vm执行生成执行函数
  20. const compiledWrapper = wrapSafe(filename, content, this);
  21. //执行函数
  22. const exports = this.exports;
  23. const thisValue = exports;
  24. const module = this;
  25. if (inspectorWrapper) {
  26. result = inspectorWrapper(compiledWrapper, thisValue, exports,
  27. require, module, filename, dirname);
  28. } else {
  29. //静态方法Reflect.apply(target, thisArgument, argumentsList)
  30. //通过指定的参数列表发起对目标(target)函数的调用
  31. result = ReflectApply(compiledWrapper, thisValue,
  32. [exports, require, module, filename, dirname]);
  33. }
  34. return result;
  35. }
  36. 6function wrapSafe(filename, content, cjsModuleInstance) {
  37. /* 生成包裹函数字符:
  38. let wrap = function(script) {
  39. return Module.wrapper[0] + script + Module.wrapper[1];
  40. };
  41. const wrapper = [
  42. '(function (exports, require, module, __filename, __dirname) { ',
  43. '\n});',
  44. ];*/
  45. const wrapper = Module.wrap(content);
  46. //获取包裹函数
  47. return vm.runInThisContext(wrapper, {
  48. filename,
  49. lineOffset: 0,
  50. displayErrors: true,
  51. importModuleDynamically: async (specifier) => {
  52. const loader = asyncESM.ESMLoader;
  53. return loader.import(specifier, normalizeReferrerURL(filename));
  54. },
  55. });
  56. }
  • 后缀名扩展解析,策略模式 ```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) } ```