nodejs中commonjs加载模块的原理

174 阅读3分钟

commonjs加载模块的核心逻辑

  • 第一步:路径分析
  • 分析传入require(moduleName),以moduleName作为模块id加载对应的模块,模块路径为module.path作为基本路径。用path模块来处理路径。
  • 调用fs模块,用const absPath= path.resolve(__diename, filename);得到文件路径
  • fs.existsSync(absPath)为true,则加载absPath对应的文件。
  • fs.existsSync(absPath)为false,则与absPath后缀'.JS','.JSON','.node'进行补足,若补足后还不存在对应路径,则到node_module下的package.json中的main字段中的文件作为模块路径返回。
  • 第二步:缓存优化
  • 查看当前传入的id对应的模块是否已经存在缓存,若有则从缓存中获取
  • 在Module下定义cache属性,Module.cache用来存储已加载的模块,const cacheModule = Module.cache[absPath]存在,if (Module.cache[absPath]) return cacheModule.export;返回缓存的模块。
  • Module.cache[absPath]不存在,则加载模块let module = New Module(absPath); 再缓存模块,Module.cache[absPath] = module;执行加载module.load();
  • 第三步:文件定位
  • 根据传入的id和module.path,拼接为模块对应的文件。用fs模块读取出文件中的字符串内容
  • 使用fs读取absPath模块文件的内容let content = fs.readFileSync(absPath, 'utf-8');
  • 第四步:编译执行
  • 将读取出的文件字符串内容content,执行content = "(function(exports, require, module, __fileName, __dirName){ content })"拼接为函数,再调用vm模块传入拼接的函数字符串,使模块得到执行。
  • 调用vm模块,let compileFn = vm.runThisContext(content);调用字符串形式的content。得到compileFn函数
  • 准备参数值,
  • let exports = module.exports
  • dirName = path.dirName(absPath);
  • fileName = absPath;
  • 调用compileFn

compileFn.call(exports, require, module, fileName, dirName);完成了js文件模块的加载和执行。

commonjs加载的基本过程

esModule注意事项

// a.js模块

let age = 18;
let name = 'kkk';

// 特别注意,这里的{ age, name }并不是一个字面量对象,而是export要求的标准语法
export { age, name }; 
// b.js模块
// 特别注意,这里的{ age, name }并不是对象的结构,而是import与export的语法要求。
import { age, name } from ('./a.js');

// 这里的age和name是常量,在给age和name会报错(因为这个特性,所以在esModule中会用来声明常量),同时age和name也是a模块中对应数据的引用。
console.log(age, name);

// 特别注意,这里的{ age, name }并不是一个字面量对象,而是export要求的标准语法
export { age, name }; 

总结:

  • export { age, name };这里{ age, name }是esModule的语法要求,并不是字面量的对象
  • import { age, name } from ('./a.js');这里{ age, name }是esModule的语法要求,并不是对象结构。
  • b模块中引入的age,name在当前模块只能作为常量使用,赋值将会报错。
  • b模块中引入的age,name都是a模块中age,name的引用。

commonjs与esModule的区别

  • commonjs中引入的模块会有缓存

  • commonjs在执行时同步加载和执行模块

  • commonjs中引入的模块的数据是一个值的拷贝

  • commonjs的模块会默认存在module,export,require,__dirName, __fileName。模块加载是会将当前模块转换为函数,作为参数传递给包含当前模块的函数

  • esModule中引入的模块是值的引用

  • esModule在编译时定义加载关系,运行时进行异步加载

  • export {}是esModule要求的语法,不是字面量对象

  • import {×××, ×××} from '×××'是esModule要求的语法,不是字面量对象结构

  • import {×××, ×××} from '×××'引入的数据是常量,引入的数据在当前模块赋值会报错。