Skip to content

ES6 - 模块

ES6 为 JavaScript 引入了一个标准化的模块系统,允许开发者将代码组织成可重用、自包含的单元。模块有助于管理复杂性、提高代码可维护性,并实现更好的依赖管理。

模块本质上是一个 JavaScript 文件。默认情况下,在模块中定义的变量、函数或类是该模块私有的。要使其可从其他模块访问,您必须显式地 export(导出)它们。要在另一个模块中使用导出的实体,您需要 import(导入)它。

  • export:用于从模块中暴露函数、对象或原始值,以便其他程序/模块可以使用它们。
  • import:用于从另一个模块引入导出的成员。
  • 严格模式(Strict Mode):模块默认处于严格模式下。
  • 作用域(Scope):每个模块都有自己的顶层作用域。在模块中声明的变量不是全局的。

主要有两种类型的导出:命名导出(named exports)和默认导出(default export)。

一个模块可以导出多个命名实体。这些实体可以在声明时导出,或者在文件末尾使用一个 export 语句统一导出。

utils.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export class Calculator {
constructor() {}
multiply(a, b) { return a * b; }
}
// 或者,在文件末尾统一导出:
// const PI = 3.14159;
// function add(a, b) { ... }
// class Calculator { ... }
// export { PI, add, Calculator };

一个模块最多只能有一个默认导出。它通常用于导出模块提供的主要实体,例如一个类或一个主函数。

logger.js
export default function logMessage(message) {
console.log(`LOG: ${message}`);
}
// 或者导出类:
// export default class User { ... }

您可以在同一个模块中同时拥有命名导出和默认导出。

要使用导出的成员,您需要使用 import 语句。

使用花括号 {} 来指定要导入哪些命名导出。

main.js
import { PI, add, Calculator } from './utils.js';
console.log(PI);
console.log(add(5, 3));
const calc = new Calculator();
console.log(calc.multiply(4, 5));
// 您也可以重命名导入:
// import { add as sumNumbers } from './utils.js';
// console.log(sumNumbers(10, 2));
// 将所有命名导出作为一个对象导入:
// import * as Utils from './utils.js';
// console.log(Utils.PI);
// console.log(Utils.add(1,1));

您可以为导入的默认实体选择任意名称。

// main.js (续)
import myLogger from './logger.js'; // 'myLogger' 可以是任意名称
myLogger('Application started.');

3. 同时导入默认导出和命名导出

Section titled “3. 同时导入默认导出和命名导出”
anotherModule.js
// export const version = '1.0';
// export default function greet() { console.log('Hello!'); }
// main.js
// import greet, { version } from './anotherModule.js';

要在 Web 浏览器中直接使用 ES 模块,您需要在 <script> 标签中添加 type="module" 属性来告诉浏览器这是一个模块脚本。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ES Modules Demo</title>
</head>
<body>
<h1>Check the console for module output!</h1>
<!-- main.js 脚本将从 utils.js 和 logger.js 中导入 -->
<script type="module" src="main.js"></script>
</body>
</html>

浏览器模块的重要注意事项:

  • 模块脚本默认是延迟执行的(它们在 HTML 解析完成后执行)。
  • import 语句中的路径是相对于导入文件位置的相对路径(例如 ./utils.js)或绝对 URL。
  • 通常需要通过 Web 服务器(即使是简单的本地服务器)来提供您的 HTML 文件和 JavaScript 模块,因为在使用 file:/// URL 导入模块时会受到 CORS 限制。
  • 对于不支持 ES 模块的旧版浏览器,您通常需要使用像 Webpack、Rollup 或 Parcel 这样的打包工具(bundler),以及像 Babel 这样的转译器(transpiler),将 ES 模块转换为旧版浏览器可以理解的格式。

Node.js 支持 ES 模块。主要有两种启用方式:

  1. 为您的模块文件使用 .mjs 文件扩展名。
  2. 或者,在您的 package.json 文件中,添加 "type": "module"。这会告诉 Node.js 将该项目中的所有 .js 文件视为 ES 模块。

package.json 中使用 "type": "module" 的示例:

package.json
{
"name": "my-node-app",
"version": "1.0.0",
"type": "module",
"main": "main.js"
}

然后您的 utils.js、logger.js 和 main.js 文件(如前所述)就可以工作了。您可以使用 node main.js 运行 main.js。

// main.js (Node.js 示例)
import fs from 'fs'; // 导入一个内置 Node.js 模块
import _ from 'lodash'; // 导入一个第三方包(需要先执行 npm install lodash)
fs.writeFileSync('test.txt', 'Hello from Node.js module!');
console.log(_.capitalize('hello world'));

ES2020 引入了动态导入,允许您按需加载模块。import() 函数返回一个 Promise 对象。

async function loadUtility() {
if (someCondition) {
const utilsModule = await import('./utils.js');
utilsModule.add(10, 20);
}
}

这对于代码拆分(code splitting)、条件加载或加载在运行时确定路径的模块非常有用。

  • 组织性:将大型应用程序分解为更小、更易于管理的块。
  • 可重用性:代码可以轻松地在应用程序的不同部分或不同项目中重复使用。
  • 封装性:隐藏模块的内部实现细节,仅暴露公共 API。
  • 依赖管理:清晰地定义代码不同部分之间的依赖关系。