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 '×××'引入的数据是常量,引入的数据在当前模块赋值会报错。