这是一个 ES6 的特性( ES6 Compatibility Table)。
在此之前有 AMD (Require.js 为代表)和 CommonJS (Node.js 为代表)两套规范,我认为不需要再去学他们,ES6 Module 应该是后续会发展得更好的模块系统。
基础
- ES6 每个模块以一个 JS 文件形式存在
- 这个文件是用来被导入的,在里面定义任何符号都不会污染全局空间
- 这个文件自动启用了 Strict Mode
语法
Basic Usage
导出单个符号:
export function detectCats() {
// ...
}
export class Kittydar {
// ...
}
导入单个符号:
// 注意:这里不管导入一个还是多个,都必须加上花括号
import {detectCats} from "./module.js"
批量导出:
function detectCats() {
// ...
}
class Kittydar {
// ...
}
// 注意:这里不管导出一个还是多个,都必须加上花括号
export {detectCats, Kittydar};
批量导入:
import {detectCats, Kittydar} from "./module.js"
导出符号重命名:
export {detectCats as dc, Kittydar as k};
导入符号重命名:
import {detectCats as dc, Kittydar as k} from "./module.js"
Default exports
导出:
// 第一种
let myObject = {
field1: value1,
field2: value2
};
export {myObject as default};
// 第二种
export default {
field1: value1,
field2: value2
};
导入:
// 下面的 moduleDefault 是个 object,有 field1, field2(对应上面的导出代码)
// 注意:这里 import 后面没有花括号
import moduleDefault from "module.js";
Module objects
import * as cows from "cows";
// 此时 cows 变量是个 object,包含 cows.js 中所有被 export 的内容
Aggregating modules
注意这里面 *
的用法,与 module objects 做对比。
// world-foods.js - good stuff from all over
// import "sri-lanka" and re-export some of its exports
export {Tea, Cinnamon} from "sri-lanka";
// import "equatorial-guinea" and re-export some of its exports
export {Coffee, Cocoa} from "equatorial-guinea";
// import "singapore" and export ALL of its exports
export * from "singapore";
如何使用?
浏览器和 Node.js 目前(2017 年 2 月)都无法直接支持 ES6 模块系统,需要转译(transpiler)和打包(bundle)工具来将其转成可被支持的代码。
最常用的 transpiler 是 Babel;最常用的打包工具是 webpack。
对于 Babel,照它的 Setup 文档 作配置即可。同时 babel-cli
库提供了 babel-node
程序,可以先转译后再调用 node
命令行程序执行 JS 代码。
webpack 相关的配置,参考它的 Getting Start 教程。
Babel 默认不支持相对于固定路径的 import,参考 JavaScript: Module System: Absolute Import。
设计原则
ES6 规范不关心解释器如何装载一个模块,但是它用一些要求限定了解释器的行为,使得这套模块系统非常静态化(可以与 Python 的做对比):
import
和export
必须在一个模块的顶层作用域被使用,无法在函数块中使用,也无法实现 conditional import or export- 所有导出的标识符必须是显式的,即不能用编程的方式导出标识符(比如如果你想导出
a1
,a2
, ...,a100
,那你必须在代码里输出这么多标识符的名字,而无法用一段循环来导出) - Module objects are frozen. 你没法 hack 进这个对象去修改它的内容,或者增加功能
- 任何模块以及它的依赖,都必须在 JS 代码被执行之前加载完毕。没有懒加载的能力。
- 导入出错时没有恢复的能力(webpack 之类的工具可以在编译期检测出这种错误)
- 整个装载过程,没有提供 hook 的能力;你没法在一个模块将被装载前执行什么代码
这里面的限制都是为了保证这个系统在运行时的稳定性。但是其中有一些能力,可以借助 webpack 这类工具实现,比如 hook 能力。而且这套规范的设计也很大程度上方便了这些 compile-time 工具。
浏览器上暂时没有实现这套加载能力,需要依赖 webpack 把不同模块打包到一个大文件中。这让我觉得很奇怪,毕竟浏览器还是作为 JS 的最大运行环境,如果不能实现这套能力真是很尴尬。但是 ES6 Module 系统是否是为了解决 AMD 和 CommonJS 割裂出来的生态系统而做的整合呢?