# ESM模块化原理
- import 变量有提升
- 执行前按顺序解析所有import变量
- 生成依赖关系图
- 执行
- 执行过程中遇到循环依赖
- 先赋值undefined
- 有一个动态引用
- import依赖的包执行完了得到了对应的export值
- 动态引用解析为正确的值
# 动态引用
- index.html
<body> <script src="./a.js" type="module"></script> </body>
1
2
3 - a.js
import { b, add, show } from "./b.js"; add(); add(); add(); show(); // 3 console.log('a文件中的b:', b); // 3
1
2
3
4
5
6
7
8 - b.js
let b = 0; const add = () => b++; const show = () => console.log('b的值:', b); export { b, add, show }
1
2
3
4
5
6
a中数值也在同步增加,说明a、b拿的是同一个值,所以说
ESM是值的引用
- a.js
const { b, add, show } = require("./b.js"); add(); add(); add(); show(); // 3 console.log('a文件中的b:', b); // 0
1
2
3
4
5
6
7
8 - b.js
let b = 0; module.exports.b = b; module.exports.add = () => b++; module.exports.show = () => console.log('b的值:', b);
1
2
3
4
5
a和b的值不同,说明不是同一个值,所以
CommonJS是值的拷贝
# 循环依赖
- index.html
<body> <script src="./a.js" type="module"></script> </body>
1
2
3 - a.js
import b from './b.js'; console.log(b); // b const a = 'a'; export default a;
1
2
3
4
5 - b.js
import a from './a.js'; // 报错 / undefined ,根据环境不同 // console.log(a); // setTimeout(() => console.log(a)); // a const b = 'b'; export default b;
1
2
3
4
5
6
7
8
9
ESM执行有一个依赖关系图,
a -> b
,b -> a
,
- a文件执行,export
变量提升
导出a,但是a变量并没有初始化。执行import b - b文件执行,import拿到变量a,但a文件并没有执行到初始化变量a的位置,所以变量a未初始化
- b文件使用a报错,因为未初始化变量a
- b执行完了,所以导出的b变量也初始化完成了
- a文件执行,正常使用b变量
- 如果b文件不同步使用a变量,就不会出现这个问题,因为使用的时候a文件已经执行完毕,变量a初始化完成了
- a.js
const { b } = require('./b.js'); console.log(b); // b const a = 'a'; module.exports.a = a;
1
2
3
4
5 - b.js
const { a } = require('./a.js'); console.log(a); // undefined const b = 'b'; module.exports.b = b;
1
2
3
4
5
CommonJS执行记录依赖过的对象,exports会变量提升,依赖过一次,就用上一次的文件解析到的位置进行
eval()
执行。
- a执行
exports变量提升
,但是未赋值,全是undefined - a执行到require("b"),
中断
执行去读取b文件 - b文件依赖a文件,CommonJS有a文件执行记录,所以返回a文件
中断前已执行的部分
,由于a没执行完,所以拿到变量a为undefined - b执行完返回执行a文件,所以a文件拿到正确的值
# 总结
- EMS本质是:依赖关系图 + 值的引用
- CommonJS本质是:eval函数 + 值的拷贝
- 共同点是:export变量提升 + 挂起本文件执行、先执行被引用文件