ESModule
- ESModule (esm) 是 ES6 的模块化方案,使用
import
&export
进行导入导出 - Node 及浏览器均支持 esm
- 在 .html 文件中应用模块化,需要给 script 标签设置
type="module"
- Node 默认支持 cjs 模块化规范。若想 Node 支持 esm,需要:
① 确保 Node 版本 >= 14.15.1
② 在 package.json 的根节点中配置"type": "module"
- 在 .html 文件中应用模块化,需要给 script 标签设置
- 导入时 [路径]、[后缀] 都要写全!!!
默认导入导出
- 一个模块只能有一个默认导出:
export default XXX
- 导入时接收的变量可自定义,符合标识符命名规范即可
demo1:
/* esm1.js */
let name = "superman";
export default name;
/* esm2.js */
import esm1 from "./esm1.js";
console.log("esm1", esm1); // esm1 superman
demo2:
/* esm1.js */
let name = "superman";
function showName() {
console.log("name", name);
}
export default { name, showName };
/* esm2.js */
import esm1 from "./esm1.js";
console.log("esm1", esm1); // esm1 { name: 'superman', showName: [Function: showName] }
按需导入导出
- 一个模块可以有多个导出
- 导入时接收的变量需要与导出的标识符一样
- 导入时可以使用
as
重命名 - [按需导入导出] 可与 [默认导入导出] 一起使用
/* esm1.js */
export let name = "superman";
export function showName() {
console.log("name", name);
}
export default { age: 21 }; // 默认导出
/* esm2.js */
import esm1, { name as myName, showName } from "./esm1.js";
console.log("esm1", esm1); // esm1 { age: 21 }
console.log("myName", myName); // myName superman
console.log("showName", showName); // showName [Function: showName]
- 导入时可用
*
接收被导入文件的所有导出,需要配合as
取别名使用:
/* esm2.js */
import * as esm1 from "./esm1.js";
console.log("esm1", esm1);
esm1 [Module: null prototype] {
default: 21,
name: 'superman',
showName: [Function: showName]
}
直接执行模块
- 导入文件时,会先执行被导入的文件,然后获取该文件导出的数据
- 如果只想执行指定文件,而无需获取该文件导出的数据,可直接使用
import "./XXX.js"
/* esm1.js */
console.log("esm1");
/* esm2.js */
import "./esm1.js";
esm 模块化特点
① 每个模块都有自己作用域
<script>
let name = "superman"
</script>
<script>
console.log(name); // superman
</script>
<script type="module">
let name = "superman"
</script>
<script type="module">
console.log(name); // 输出空值
</script>
② 模块会被延迟解析
默认情况下,HTML 文件是从上往下解析的。如果把 script 标签放在 body 标签的前面,则无法获取 DOM 元素
<body>
<div>superman</div>
</body>
<script>
console.log(document.querySelector("div"));
</script>
但是,如果我们给 script 标签添加属性 type="module"
,则会等页面加载完成后,才执行 script 标签里面的语句
<script type="module">
console.log(document.querySelector("div"));
</script>
<body>
<div>superman</div>
</body>
③ 使用 esm 会将 JS 的运行模式变成 [严格模式](默认情况下,不是 [严格模式])
<script type="module">
a = 10; // 会报错;严格模式下,变量必须被定义
</script>
④ 动态绑定
- 使用
export
按需导出的是数据的引用,就是说 [源数据] 更新后,导入的数据也会更新
/* esm1.js */
export let age = 18;
setTimeout(() => (age = 21), 1000); // 一秒后改变 age 值
/* esm2.js */
import { age } from "./esm1.js";
console.log(age); // 18
setTimeout(() => {
// 两秒后重新获取 age 值
console.log(age); // 21
}, 2000);
- 但是使用
export default
默认导出的数据不会动态绑定
/* esm1.js */
let age = 18;
export default age;
setTimeout(() => (age = 21), 1000); // 一秒后改变 age 值
/* esm2.js */
import age from "./esm1.js";
console.log(age); // 18
setTimeout(() => {
// 两秒后重新获取 age 值
console.log(age); // 18
}, 2000);
import 注意事项
① 即使重复写 import 语句,都只会导入一次
/* esm1.js */
console.log("esm1");
/* esm2.js */
import "./esm1.js";
import "./esm1.js";
import "./esm1.js";
看见,"esm1"
只输出了一次
②
import
语句提升
-
import
语句有提升效果,会提升到整个模块的最前面,首先执行这是因为
import
语句是编译阶段执行的,在代码运行之前 -
因此,导入为静态加载,即不管用不用,都会先导入
foo();
import { foo } from 'my_module';
③
import
语句中不能使用 [表达式] / [变量]
- 由于
import
是静态执行,所以不能使用 [表达式] / [变量] 这些只有在运行时才能得到结果的语法结构
// 报错
import { 'f' + 'oo' } from 'my_module'; // 不能使用 [表达式] 接收导入数据
// 报错
let module = 'my_module';
import { foo } from module; // 不能使用 [变量] 作为被导入文件的路径
// 报错:不能在语句内进行导入
if (true)
import { foo } from 'module1';
动态导入import()
- 因为是静态引入,所以默认的
import
语法不能写在if
之类的语句里 - 如果有这样的需求,我们可以使用
import()
动态导入:
if (true)
import('./1.js');
-
注意:动态引入,不会有
import
提升 (毕竟都不再是编译阶段执行了,变成运行时执行了) -
import()
动态引入,返回一个Promise
对象then()
的参数是导入的模块对象
import('./index2.js').then(res => {
console.log(res); // Module {Symbol(Symbol.toStringTag): "Module"}
});
模块化 demo
思路:[ 数据 ]、[ 方法 ]、[ 逻辑处理 ] 分开放置
/* demo.js */
let a = 20;
let b = 30;
export { a, b };
/* demo2.js */
import { a, b } from "./demo.js";
const sum = () => a + b
export default sum;
<script type="module">
import sum from '../js/demo2.js'
console.log(sum()); // 50
</script>