模块语法是ES6
的一个重要特性,它的出现让JavaScript
的模块化编程成为了可能。
在JavaScript
中可以直接使用import
和export
关键字来导入和导出模块,但是这种语法并不是ES6
的标准,而是ESM
(ECMAScript Module
)模块语法的一部分。
虽然导入和导出单个模块的语法非常简单,但是还有许多其他的方式,让你可以使用ESM
模块语法来导入和导出模块。
ESM 模块语法
ESM
模块语法很简单,它只有两个关键字:import
和export
。
export
export
关键字用来导出模块,每个模块都有一个默认导出,可以使用export default
关键字来导出。
// 导出一个变量
const name = '田八';
export default name;
这种导出方式可以导出任何类型的数据,包括基本数据类型、对象、函数等。
但是必须得先定义变量,然后再导出,不能在导出的过程中定义变量。
// 错误的导出方式
export default const name = '田八';
对于函数和类的声明,可以无需定义变量,直接导出。
// 导出一个函数
export default function Hello () {
console.log('hello world');
};
// 导出一个类
export default class Hello {
constructor () {
console.log('hello world');
}
}
对于字面量类型的数据,可以直接导出。
// 导出一个对象
export default {
name: '田八',
age: 18
};
// 导出一个数组
export default [1, 2, 3, 4, 5];
// 导出一个字符串
export default 'hello world';
// 导出一个数字
export default 123;
命名导出
任何变量都可以在创建时使用export
关键字来导出,这种导出方式叫做命名导出。
// 导出一个变量
export const name = '田八';
// 导出一个函数
export function Hello () {
console.log('hello world');
};
// 导出一个类
export class Hello {
constructor () {
console.log('hello world');
}
}
如果想要导出多个变量,可以使用export
关键字,然后在花括号中写上变量名。
// 导出多个变量
const name = '田八';
const age = 18;
export {
name, age };
如果对导出的变量名不满意,可以使用as
关键字来重命名。
// 重命名导出的变量
const name = '田八';
const age = 18;
export {
name as username, // 重命名为 username
age // 不重命名
};
导出所有
如果想要导出一个模块中的所有变量,通常我们都是像下面这样写的:
import module from './module.js';
// 导出所有变量
export {
...module
}
其实有一种更简单的方式,就是使用export * from
关键字。
// 导出所有变量
export * from './module.js';
这种方式将会导出module.js
模块中的所有变量,但是不会导出module.js
模块中的默认导出,因为一个模块只能有一个默认导出。
不过export
也提供了一种方式来导出其他模块的默认导出:
// 导出其他模块的默认导出
export {
default } from './module.js';
也可以选择性的导出其他模块的不同变量:
// 导出其他模块的不同变量
export {
name, age } from './module.js';
同样的,也可以使用as
关键字来重命名导出的变量。
// 重命名导出的变量
export {
name as username, age, default as module } from './module.js';
// 导出其他模块的所有变量,并重命名
export * as module from './module.js';
import
import
关键字用来导入模块,通常情况下是直接导入模块中的默认导出。
// 导入模块中的默认导出
import module from './module.js';
如果想同时导入模块中的默认导出和其他变量,可以在花括号中写上变量名。
// 导入模块中的默认导出和其他变量
import module, {
name, age } from './module.js';
如果想要导入模块中的所有变量,可以使用import * as
关键字。
// 导入模块中的所有变量
import * as module from './module.js';
这样就可以通过module
对象来访问模块中的所有变量了。
命名导入
和命名导出一样,任何变量都可以在创建时使用import
关键字来导入,这种导入方式叫做命名导入。
// 命名导入
import {
name, age } from './module.js';
也可以使用as
关键字来重命名导入的变量。
// 重命名导入的变量
import {
name as username, age } from './module.js';
还可以混用命名导入和默认导入。
// 混用命名导入和默认导入
import module, {
name, age } from './module.js';
上面的代码中,module
就是默认导入,name
和age
就是命名导入。
同时,还可以不导入任何变量,只是执行模块中的代码。
// 只执行模块中的代码
import './module.js';
这样做的好处是可以在模块中执行一些初始化代码,比如在模块中注册全局变量,这种方式称为import for side effects(副作用导入)
。
动态导入
import
关键字是静态的,也就是说,它必须在模块的顶层使用,不能在函数或者if
语句中使用,或者其他代码块中使用。
// 错误的写法
function loadModule() {
import './module.js';
}
if (true) {
import './module.js';
}
{
import './module.js';
}
上面的代码中,import
关键字都是错误的,因为它们都不是模块的顶层。
为了解决这个问题,ES2018引入了import()
函数,它可以在任何地方使用,只要是在模块中就可以。
// 正确的写法
function loadModule() {
import('./module.js');
}
if (true) {
import('./module.js');
}
{
import('./module.js');
}
因为ES
模块是异步加载的,所以import()
函数返回一个Promise
对象。
import('./module.js').then(module => {
// ...
});
如果没有找到模块,import()
函数会抛出一个错误,所以注意捕获异常。
import('./module.js').catch(err => {
// ...
});
// 或者
try {
await import('./module.js');
} catch (err) {
// ...
}
import()
函数可以用在任何地方,不仅仅是模块。
总结
ES
模块是JavaScript
模块的标准,它的设计目的是为了取代CommonJS
模块,成为浏览器和服务器通用的模块解决方案。
最后请记住,导出和静态导入只能在模块的顶层使用,不能在函数或者if
语句,或者任何代码块中使用,而动态导入可以在任何地方使用。