从 ES6 开始,在语言标准的层面上,实现了模块化功能(ES6 Module),而且实现得相当简单,完全可以取代 CommonJS 和 CMD、AMD 规范,成为浏览器和服务器通用的模块解决方案。
任何模块化,都必须考虑的两个问题就是导入依赖和导出接口。ES6 Module 也是如此,模块功能主要由两个命令构成:export 和 import。export 命令用于导出模块的对外接口,import 命令用于导入其他模块导出的内容。
模块文件
ES6 使用 .js 或者 .mjs 扩展名的文件定义 模块。(建议使用 .js)
import 和 export 语句只能在模块内部使用,不能在普通脚本文件中使用。
浏览器通过 script 标签的 type 属性来判断一个 .js 文件/内部脚本是模块文件还是普通脚本文件。例如:
<script type="module" src="main.js"></script>
Node.js 通过 package.json 中的 type 来判断 .js 文件是当作 ES6 模块文件 (module) 还是 CommonJS 模块文件 (commonjs)。
导出模块的功能
使用 export 语句将一个模块的功能导出给其他模块使用。能够导出函数、变量和类。export 要放在模块的最外层。
分别导出
在函数、变量或类之前加上 export 就可以分别导出这些功能:
export const name = "square";
export function draw(){...};
export class Square {...};
集中导出
使用 export 加上花括号可以集中导出定义好的功能:
export { name, draw, reportArea, reportPerimeter };
重命名导出
如果想为这些功能取一个新的名字,可以使用 as 关键字重命名导出:
export { function1 as newFunctionName, function2 as anotherNewFunctionName };
导入模块
分别导入
使用 import 语句导入其他模块来使用其导出的功能:
import { name, draw, reportArea, reportPerimeter } from '/square.js';
import后被花括号包围的用逗号分隔的是想导入的功能列表,from后是模块文件的路径。- 模块文件的路径以
/开头是相对于站点根目录的路径。以.开头表示相对当前文件的路径。必须提供文件扩展名。
导入后就可以使用响应的功能了。
重命名导入
如果想为这些功能取一个新的名字,避免命名冲突,可以使用 as 关键字重命名导入:
import {
function1 as newFunctionName,
function2 as anotherNewFunctionName,
} from "/modules/module.js";
作为模块对象全部导入
如果不想分别导入,可以导入模块的全部功能到一个模块功能对象上,并为这个对象取一个名字:
import * as Module from "/modules/module.js";
这样模块的全部功能都在 Module 对象上,可以通过如下方式使用:
Module.function1();
Module.function2();
无命名导入
只是运行模块的代码,不使用其导出的任何功能:
import './module.js';
使用默认导出和导入
默认导出
使用 default 关键字导出的内容为默认导出:
export default randomSquare;
每个模块只允许有一个默认导出。
导入默认值
默认导入时无需使用花括号:
import randomSquare from "./modules/square.js";
相当于下面的缩写:
import { default as randomSquare } from "./modules/square.js";
如果使用全部导入 (import *),默认导出的内容在模块对象的 default 属性上。
导入后导出
使用下面的简写语法可以在导入后重新导出部分或全部导出子模块的功能:
export * from "x.js";
export { name } from "x.js";
动态导入
浏览器中可用的 JavaScript 模块功能的最新部分是动态模块加载。这允许你仅在需要时动态加载模块,而不必预先加载所有模块。
这个新功能允许你将 import() 作为函数调用,将模块的路径作为参数传递。它返回一个 Promise,它用一个模块对象来实现 Promise,让你可以通过模块对象访问该模块导出的功能,例如:
import("/modules/mymodule.js").then((module) => {
// 用这个模块做点什么...
});
// 或者将模块对象解构
import('/modules/mymodule.js').then(({ default: DefaultExport, NamedExport }) => {
// 用这个模块做点什么...
});
如果在模块顶层使用 import() 仍然会在模块运行时导入子模块,可以使用下面的方法将模块封装成一个函数:
const MyModule() => import("/modules/mymodule.js")
只有在运行 MyModule() 时才会导入子模块。
参考
指南:JavaScript 模块
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules
Node.js ECMAScript 模块
https://nodejs.cn/api/esm.html
提升(import 提升)
https://developer.mozilla.org/zh-CN/docs/Glossary/Hoisting