ES6(ECMAScript 2015)引入了模块化的概念,旨在使 JavaScript 更加模块化、可维护和可重用。ES6 模块允许我们在不同的文件中组织和管理代码,使得不同模块之间的依赖关系更加清晰。
1. 导出(Export)
1.1 命名导出(Named Exports)
命名导出允许你导出多个变量、函数或类,每个导出的名称必须是唯一的。
// file1.js
export const name = 'John';
export function greet() {
console.log('Hello!');
}
export class Person {
constructor(name) {
this.name = name;
}
}
使用命名导出时,你可以在导入时使用相同的名称来访问这些导出。
// file2.js
import { name, greet, Person } from './file1';
console.log(name); // John
greet(); // Hello!
const person = new Person('Jane');
1.2 默认导出(Default Export)
每个模块只能有一个默认导出。默认导出的语法更加简洁,可以导出一个值(如对象、函数、类等)。
js
// file1.js
const person = {
name: 'John',
age: 30
};
export default person;
导入默认导出的方式没有花括号。
js
// file2.js
import person from './file1';
console.log(person.name); // John
1.3 导出重命名(Export Rename)
你可以在导出时使用 as 进行重命名。
js
// file1.js
const firstName = 'John';
export { firstName as name };
1.4 导出合并(Export All)
你可以一次性将一个模块的所有导出重新导出,适用于模块间的组合。
js
// file1.js
export const name = 'John';
// file2.js
export * from './file1'; // 导出 file1.js 中所有的导出
2. 导入(Import)
2.1 导入命名导出(Named Imports)
// file1.js
export const name = 'John';
export function greet() {
console.log('Hello!');
}
// file2.js
import { name, greet } from './file1';
console.log(name); // John
greet(); // Hello!
2.2 导入默认导出(Default Import)
js
// file1.js
const person = { name: 'John' };
export default person;
// file2.js
import person from './file1';
console.log(person.name); // John
2.3 导入重命名(Import Rename)
导入时使用 as 可以对导入的模块进行重命名。
// file1.js
export const firstName = 'John';
// file2.js
import { firstName as name } from './file1';
console.log(name); // John
2.4 导入全部(Import All)
你可以一次性导入一个模块的所有命名导出,并将其作为一个对象使用。
// file1.js
export const name = 'John';
export const age = 30;
// file2.js
import * as person from './file1';
console.log(person.name); // John
console.log(person.age); // 30
2.5 动态导入(Dynamic Import)
ES6 模块支持动态导入,返回一个 Promise,可以根据需要异步加载模块。
// 动态导入
import('./file1').then(module => {
console.log(module.name);
});
3. 模块化的特点
3.1 模块是默认严格模式
ES6 模块在模块内部默认使用严格模式(‘use strict’;),因此所有模块的代码都是严格模式的代码,不需要显式声明。
// file1.js
x = 10; // 报错,严格模式下不允许未声明的变量
3.2 模块的作用域
每个模块都有自己的作用域,不会污染全局作用域。模块之间通过 import 和 export 进行通信。
// file1.js
let counter = 0;
export function increment() {
counter++;
}
// file2.js
import { increment } from './file1';
increment();
console.log(counter); // 由于作用域隔离,counter 在 file2.js 中不可访问
3.3 循环依赖
ES6 模块系统解决了模块间的循环依赖问题。对于导入的模块,它会暂时处于“挂起”状态,直到依赖的模块加载完成。
// a.js
import { b } from './b.js';
export const a = 'a';
// b.js
import { a } from './a.js';
export const b = 'b';
console.log(a, b); // 输出: a b
3.4 只读导入
ES6 模块中的导入是只读的。你无法修改从模块导入的值。
// file1.js
export let name = 'John';
// file2.js
import { name } from './file1';
name = 'Jane'; // 错误:不能修改从模块导入的值
4. 使用模块
4.1 模块在浏览器中的使用
现代浏览器支持 module 类型的脚本。使用
<script type="module">
import { name } from './file1.js';
console.log(name);
</script>
4.2 在 Node.js 中使用 ES6 模块
在 Node.js 中,你需要使用 .mjs 文件扩展名或在 package.json 中设置 “type”: “module”,来启用 ES6 模块。
{
"type": "module"
}
然后,你就可以在 Node.js 中使用 import 和 export 语法了。
// file1.mjs
export const name = 'John';
// file2.mjs
import { name } from './file1.mjs';
console.log(name); // John
5. 总结
ES6 模块引入了更简洁的语法,使得 JavaScript 的代码结构更加清晰和可维护。通过 import 和 export,我们可以将代码拆分成小的模块,按需加载,并处理依赖关系。使用 ES6 模块化的好处包括:
- 提高代码的可维护性和可读性。
- 更好的支持循环依赖。
- 默认严格模式,避免了许多常见的 JavaScript 问题。