JS模块:ESM
跳到导航
跳到搜索
关于[1]
ESM(ECMAScript Module)在 2015 年的时候出现于 ES-6 中,逐渐取代 AMD、UMD、CJM(CommonJS)。 参考:【JS模块:ESM 与 CJM】
一个模块(module)就是一个文件,模块间通过 export 和 import 互相加载。
export:导出[2]
export 语句:用于从模块中导出实时绑定的函数、对象或原始值,以便其他程序可以通过 import 语句使用它们。 —— 被导出的绑定值依然可以在本地进行修改。 —— 在使用 import 进行导入时,这些绑定值只能被导入模块所读取,但在 export 导出模块中对这些绑定值进行修改,所修改的值也会实时地更新。
- 导出单个接口:
// 导出变量名 export let name1, name2, …, nameN; // also var, const // 导出表达式 export let name1 = …, name2 = …, …, nameN; // also var, const // 导出方法 export function FunctionName(){...} // 导出类 export class ClassName {...}
- 导出多个接口:
export { name1, name2, …, nameN };
- 重命名导出:
export { variable1 as name1, variable2 as name2, …, nameN };
- 在导入期间,必须使用相应对象的相同名称
- 解构导出并重命名:
export const { name1, name2: bar } = o;
- 默认导出:
export default expression; export default function (…) { … } // also class, function* export default function name1(…) { … } // also class, function* export { name1 as default, … };
- 一个模块只能有一个默认导出
- 导出模块合集:
export * from …; // does not set the default export export * as name1 from …; // Draft ECMAScript® 2O21 export { name1, name2, …, nameN } from …; export { import1 as name1, import2 as name2, …, nameN } from …; export { default } from …;
import:静态导入[3]
import 语句:用于导入由另一个模块导出的绑定。 —— 无论是否声明了 strict mode,导入的模块都运行在“严格模式”下。 —— 在浏览器中,import 语句只能在声明了 type="module" 的 <script> 的标签中使用。
- 导入单个接口:
import {myExport} from '/modules/my-module.js';
- 导入多个接口:
import {foo, bar} from '/modules/my-module.js';
- 重命名导入:
import {exportName1 as name1, exportName2 as name2} from '/modules/my-module.js';
- 导入一个模块作为“副作用”:
import '/modules/my-module.js';
- 运行模块中的全局代码,但实际上不导入任何值
- 导入默认值:
// 仅导入默认值 import myDefault from '/modules/my-module.js'; // 导入默认值与其他接口 import myDefault, * as myModule from '/modules/my-module.js'; import myDefault, {foo, bar} from '/modules/my-module.js';
- 导入模块合集:
import * as myModule from '/modules/my-module.js';
- 通过该对象的属性访问模块接口
- 重导出:
export { default as function1, function2 } from 'bar.js';
- 等同于“联合使用 import、export”:
import { default as function1, function2 } from 'bar.js'; export { function1, function2 };
import():动态导入[4]
import() 方法:通常称为“动态导入”,是一个类似函数的表达式,它将返回一个 Promise —— 【常配合 async/await 使用】 —— 用于将 ECMAScript 模块异步、动态地加载到潜在的非模块环境中。 ——仅在必要时使用“动态导入”——【“静态导入”更适合于加载初始依赖项,利于静态分析工具和“tree shaking”(树抖动)】
- 导入一个模块作为“副作用”:
(async () => { if (somethingIsTrue) { const { default: myDefault, foo, bar, } = await import("/modules/my-module.js"); } })();
- 运行模块中的全局代码,但实际上不导入任何值
- 响应用户操作按需导入:
const main = document.querySelector("main"); for (const link of document.querySelectorAll("nav > a")) { link.addEventListener("click", (e) => { e.preventDefault(); import("/modules/my-module.js") .then((module) => { module.loadPageInto(main); }) .catch((err) => { main.textContent = err.message; }); }); }
- 根据环境导入不同的模块:
let myModule; if (typeof window === "undefined") { myModule = await import("module-used-on-server"); } else { myModule = await import("module-used-in-browser"); }
- 导入带有非文字说明符的模块:
Promise.all( Array.from({ length: 10 }).map((_, index) => import(`/modules/module-${index}.js`), ), ).then((modules) => modules.forEach((module) => module.load()));
尽管 import() 看起来像一个函数调用,但它只是一种特殊语法,只是恰好使用了括号(类似于 super())。 因此,不能将 import 复制到一个变量中,或者对其使用 call/apply —— 因为它不是一个函数!!!
import.meta:元数据属性[5]
import.meta是一个给 JavaScript 模块暴露特定上下文的元数据属性的对象。它包含了这个模块的信息,比如说这个模块的 URL。
- import.meta.url:当前模块的 URL 路径
- import.meta.scriptElement:(浏览器特有)加载模块的那个 <script> 元素
相关用法
聚合重导出
即:在一个模块中,聚合多个模块的导出
childModule1.js 中:
let myFunction = ...; // assign something useful to myFunction let myVariable = ...; // assign something useful to myVariable export {myFunction, myVariable};
childModule2.js 中:
let myClass = ...; // assign something useful to myClass export myClass;
parentModule.js 中:聚合重导出
export { myFunction, myVariable } from 'childModule1.js'; export { myClass } from 'childModule2.js';
顶层模块中:从 parentModule.js 模块导入
import { myFunction, myVariable, myClass } from 'parentModule.js'
<script>中嵌入[6]
在浏览器中,import 语句只能在声明了 type="module"
的 script 的标签中使用。
相关属性:
- src:定义引用外部脚本的 URI,可以用来代替直接在文档中嵌入脚本。
- 指定该属性的 <script> 元素标签内不应该再有嵌入的脚本;
- type:定义 <script> 元素包含或 src 引用的脚本语言。
- 支持的 MIME 类型包括:text/javascript、text/ecmascript、application/javascript、application/ecmascript;
- 如果 type 属性为“module”,代码会被当作“JavaScript 模块”;
- async:如果存在该属性,那么“普通脚本”会被并行请求,并尽快解析和执行。
- 对于“模块脚本”:脚本及其所有依赖都会在延缓队列中执行,因此它们会被并行请求,并尽快解析和执行。
- defer:如果存在该属性,脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行。
- 该属性会阻止 DOMContentLoaded 事件,直到脚本被加载并且解析完成。
💡若需要异步加载,就必须使用async
/defer
: ——(脚本的加载,都是异步进行) —— async: 1、“执行脚本”与“文档解析”同时进行;——(脚本加载完成,就中断“文档解析”转而“执行脚本”) 2、脚本按照“加载顺序”执行(先加载完成,先执行)——【无法保证脚本间执行顺序】 —— defer: 1、“执行脚本”在“文档解析”完成之后; 2、脚本按照“定义顺序”执行(前面定义的,先执行)——【可以保证脚本间执行顺序】 📜默认情况下: —— 对于“普通脚本”:浏览器是同步加载,即:渲染引擎遇到<script>
标签就会停下来,等到脚本加载、执行完毕,再继续向下渲染。 —— 对于“模块脚本”:浏览器是异步加载(相当于使用了“defer”),此时可以同时使用“async”。
示例:
- 加载外部模块:
<script type="module" src="./foo.js"></script> <!-- 等同于 --> <script type="module" src="./foo.js" defer></script>
- 加载外部模块,并尽快执行:
<script type="module" src="./foo.js" async></script>
- 在内嵌脚本中加载模块:
<script type="module"> import utils from "./utils.js"; // other code </script>
参考
- ↑ 参考列表:
- ↑ 参考:MDN:《export》
- ↑ 参考:MDN:《import》
- ↑ 参考:MDN:《import()》
- ↑ 参考:MDN:《import.meta》
- ↑ 参考:MSN:《<script>》