ESModule 的工作原理
浏览器环境也支持 ES 模块。通过给 script 设置 type=“module” 属性即可。 工程化环境,如 webpack、vite 中也支持。
以下只讨论浏览器环境中的 ES 模块化解析和执行过程。
js
import foo from "./foo.js";
import bar from "./bar.js";
import("./dynamic.js").then((m) => {
console.log("main", m.default);
});
console.log("main", foo, bar);js
import bar from "./bar.js";
console.log("foo", bar);
export default "foo";js
console.log("bar");
export default "bar";模块解析
即, 把相关的 js 文件全部下载下来
- 找到入口文件
***/main.js,(如果是相对路径,则会转为绝对路径),然后开始下载 - 下载完成后,会进入到该 js 文件中去看顶层的静态导入语句,并把它们提到整个代码的最前边
- 递归的下载并且解析对应的 js。下载完成后,再进到它们的文件中去看静态导入语句,如果没有下载,则继续下载(下载过的就跳过,不再下载了)
- 都下载完成后,开始下一步 ———— 模块的执行
静态导入语句只能写到顶层,不能写到循环和条件语句中
模块执行
- 从入口文件开始执行,先进入到
main.js
main.js:
main.js的第一行是import foo from './foo.js,则进入foo.js,foo.js的第一行是import bar from './bar.js,则又会进入bar.js,bar.js的第一行是console.log("bar");,在控制台打印了一个bar。接着,默认导出一个字符串bar,生成一个表格,然后bar.js执行完成了。
bar.js:
| default | "bar" |
|---|---|
- 然后回到
foo.js,foo.js是通过import bar from './bar.js导入的bar.js,这里的变量bar和bar.js的默认导出是符号绑定(用的同一块内存空间),打印出一个foo bar。接着,foo.js中也有一个默认导出,也会生成一个表格,然后foo.js执行完成了。
foo.js:
| default | "foo" |
|---|---|
- 然后回到
main.js中的下一行import bar from "./bar.js";,因为bar.js已经运行过了,所以直接把bar.js表格中的内容直接取出来了。接着是一个动态导入,之前没下载过,所以先去下载解析。这里是异步的,所以主线程继续往后运行到console.log("main", foo, bar);打印对应的结果main foo bar,然后main.js运行结束。 - 等到动态的 js 下载完成后,继续进行模块的解析和执行,和刚刚的过程是一样的,然后导出的结果也会生成一个表格。然后执行
import("./dynamic.js").then((m))回调,这里的m就是dynaic.js模块生成的表格。
dynamic.js:
| default | "dynamic" |
|---|---|
模块的导出会给模块生成一个表格,每个模块最终都会变成一个表格,表格中会记录模块导出的结果。记录是为了方便缓存,以后再次导入该模块时,就不会从头到尾运行了,而是从表格中取相应的结果进行返回。