简介: 2014年7月底,TC39又召开了一次会议,最后敲定了ECMAScript 6 (ES6)模块语法的最后细节。本文概述了完整的ES6模块系统。
一、当前的模块系统
javaScript没有内置对模块的支持,可是社区为此建立了使人满意的变通方法。 而这就要说到下面两条重要的标准:
一、简洁的语法
二、为同步加载而设计的,主要是用于服务器端。规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操做
- Asynchronous Module Definition (AMD):
一、Slightly more complicated syntax, enabling AMD to work without eval() (or a compilation step).(稍微复杂一点的语法,使AMD能够在没有eval()(或编译步骤)的状况下工做。这个没太懂,大概是指语法会复杂一点,可是在未变异状况下能够工做,我去搜一下AMD的相关资料,找到一句话,不知道是否是匹配这里。
AMD同时是“匿名的”,意味着模块不须要硬编码指向其路径的引用, 模块名仅依赖其文件名和目录路径,极大的下降了重构的工做量)
二、为异步加载而设计的,主要是用于浏览器端。AMD规范则是非同步加载模块,容许指定回调函数
因为Node.js主要用于服务器编程,模块文件通常都已经存在于本地硬盘,因此加载起来比较快,不用考虑非同步加载的方式,因此CommonJS规范比较适用。可是,若是是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,所以浏览器端通常采用AMD规范。
二、ECMAScript 6 modules
es6 modules 的目的是建立一种能被CommonJS和AMD用户都喜欢的语法格式:
- 与CommonJS相似,它们具备紧凑的语法、对单个导出的偏好以及对循环依赖的支持
- 与AMD相似,它们直接支持异步加载和可配置模块加载
ES6模块超越CommonJS和AMD的地方:
- 它们的语法比CommonJS的语法更简洁
- 它们的结构能够进行静态分析(用于静态检查、优化等)
- 它们对循环依赖项的支持优于CommonJS
ES6模块标准分为两个部分:
- import 和 export 语法 (即 named exports 和 default export,named import 和 default import,详情可看此文章)
- 二、Programmatic loader API: to configure how modules are loaded and to conditionally load modules(程序化的加载API: 配置模块的加载方式并有条件地加载模块。没懂)
三、ES6模块语法概述
有两种导出:named exports(每一个模块能够有多个 named exports)和 default export (每一个模块只容许至多有一个)
3.1 Named exports
模块能够经过在声明前加上关键字export来导出多个东西。这些导出以它们的名称来区分,称为命名导出

若是须要,还能够导入整个模块,并经过属性表示法引用其命名的导出

相同的实如今CommonJS里以下:

3.2 default export
只导出单个值的模块在node.js社区中会常常碰碰到。可是它们在前端开发中也很常见,在前端开发中,您常常为模型提供构造函数/类,每一个模块有一个模型。ECMAScript 6模块能够选择default export,这是最重要的导出值。default export 特别容易导入。

default export 是 class 的ECMAScript 6模块以下所示

注意: default export 导出的是一个匿名表达式。它将经过模块的名称来标识,如上面两个例子中的myFunc 和MyClass。
3.3 在一个模块里能够同时存在 named exports 和 default export
下面的模式在JavaScript中很是常见的: 库是一个函数,可是经过该函数的属性提供了其余服务这是很常见的,在jQuery和Underscore.js常常会碰到这种场景。
在 CommonJS 实现以下:

上面的例子若是是用es6 modules写的话就是以下:

请注意,CommonJS 实现和ECMAScript 6 实现只是大体类似。后者具备扁平结构,而前者是嵌套的。您喜欢哪一种风格是一个品味问题,可是扁平风格具备静态可分析的优势(为何这很好,将在下面解释)。CommonJS风格的部分目的彷佛是须要对象做为名称空间,这种须要一般能够经过ES6模块的 named exports 来实现。
default export 能够看做是一种特别的 named export
default export 实际上只是具备特殊名称default的 named export。也就是说,下面两个表述是等价的

而下面的这两种写法也是等价的。

四、设计目标
若是你想理解ECMAScript 6模块,它有助于理解什么目标影响了它们的设计,主要分如下几个方面:
- 默认导出的设计
- 静态模块结构
- 支持同步和异步加载
- 支持模块之间的循环依赖关系
4.1 default export
模块语法代表default export “是”模块可能看起来有点奇怪,可是若是您认为一个主要的设计目标是使默认导出尽量方便,那么这是有意义的。
4.2 静态模块结构
在当前的JavaScript模块系统中,您必须执行代码,以查明导入和导出是什么。这就是ECMAScript 6与这些系统不一样的主要缘由:经过将模块系统构建到该语言中,您能够从语法上强制执行一个静态模块结构。让咱们先看看这意味着什么,而后看看它带来了什么好处。
模块的结构是静态的,这意味着您能够在编译时(静态地)肯定导入和导出——您只须要查看源代码,没必要执行它。下面是CommonJS模块如何让这成为不可能的两个例子。在第一个示例中,您必须运行代码来查找它导入的内容:

接下来您必须运行代码来查找它导出的内容
ECMAScript 6给了你较少的灵活性,它强迫你保持静态。所以,您将得到的几个好处,下面将对此进行描述:
优势1: 能够更快的查找导入文件内容
若是用CommonJS的方式引入一个库,就会返回一个对象

所以,经过这种方式导入库, lib.someFunc 意味着你必须进行属性查找,由于它是动态的,因此会更慢。
相反,若是你用es6的方式引入一个库,您能够静态地了解其内容并优化访问。

优势2: 变量检查
使用静态模块结构,您老是静态地知道哪些变量在模块内的任何位置可见:
- 全局变量: 惟一彻底的全局变量未来自语言自己。其余一切都未来自模块(包括来自标准库和浏览器的功能)。也就是说,您静态地知道全部全局变量
- 模块导入:您也静态地知道这些
- 模块局部变量:能够经过静态检查模块来肯定。
这有助于检查给定标识符是否正确。这种检查是JSLint和JSHint等的一个流行特性;在ECMAScript 6中,大部分能够由JavaScript引擎执行。
优势3: 为宏的支持作准备
宏仍然在JavaScript的将来路线图上。若是JavaScript引擎支持宏,能够经过库向其添加新语法,sweet.js是一个实验性的JavaScript宏系统,下面是来自The Sweet网站的一个例子: 一个类的宏。

对于宏,JavaScript引擎在编译以前执行预处理步骤:若是解析器生成的令牌流中的令牌序列与宏的模式部分匹配,则由宏体生成的令牌替换。预处理步骤只有在可以静态地找到宏定义时才有效。所以,若是您想经过模块导入宏,那么它们必须具备静态结构。
优势4: 为类型系统作准备
静态类型检查强加了相似于宏的约束:只有在静态地找到类型定义时才能执行。一样,只有具备静态结构的模块才能导入类型。
优势5: 支持其余语言
若是您但愿支持将带有宏和静态类型的语言编译成JavaScript,那么JavaScript的模块应该具备静态结构,缘由见前两节。
4.3 支持同步和异步加载
ECMAScript 6模块必须独立于引擎是同步加载模块(例如在服务器上)仍是异步加载模块(例如在浏览器中)。它的语法很是适合同步加载,异步加载是由它的静态结构支持的:由于您能够静态地肯定全部导入,因此您能够在评估模块体以前加载它们(让人想起AMD模块的方式)。
4.4支持模块之间的循环依赖
若是A(多是间接/直接的)导入B和B导入A,那么两个模块A和B是循环依赖的,若是可能的话,应该避免循环依赖,它们致使A和B紧密耦合——它们只能一块儿使用和改变。
为何须要支持循环依赖?
循环依赖自己并非坏事。特别是对于对象,有时甚至须要这种依赖性。例如,在一些树(如DOM文档)中,父节点引用子节点,子节点引用父节点。在库中,一般能够经过仔细设计来避免循环依赖。可是在大型系统中,它们可能会发生,特别是在重构期间。若是模块系统支持它们,那么它将很是有用,由于在重构时系统不会崩溃。
让咱们看看CommonJS和ECMAScript 6是如何处理循环依赖关系的。
在commonJS里的循环依赖
在CommonJS中,若是模块B引入当前正在计算其主体的模块a,它将返回A当前状态下的导出对象(下例中的第1行)。这使B可以引用其导出中该对象的属性(第2行)。属性在B的处理完成后填写,此时B的导出工做正常。

做为通用规则,请记住,对于循环依赖项,您不能访问模块主体中的导入。这是这种现象固有的,不会随着ECMAScript 6模块的改变而改变。
CommonJS方法的局限性是:
一、在node.js里,多个值的时候是不能导出一个单独的值的,只能导出对象。你能够像下面这样:
module.exports = function () { ... }
若是在模块A中这样作,就不能在模块B中直接使用导出函数,由于B的变量A仍然引用A的原始导出对象。
二、不能直接使用命名导出。也就是说,模块B不能像这样导入a.foo:
var foo = require('a').foo;
foo是没有定义的。换句话说,您别无选择,只能经过导出对象a引用foo。
CommonJS有一个独特的特性:能够在导入以前导出。保证在导入模块的主体中能够访问这些导出。也就是说,若是A这么作了,它们就能够进入B的体内。然而,导入前导出不多有用。
在es6里面的循环引用
为了消除上述两个限制,ECMAScript 6模块导出是模块的引用,而不是值。也就是说,到模块主体中声明的变量的链接仍然是活动的。下面的代码演示了这一点。

所以,面对循环依赖关系,您是直接访问命名导出仍是经过其模块访问命名导出并不重要:在这两种状况下都存在间接关系,并且它老是有效的。
五、关于导入和导出的一些知识
5.1 导入
es6 提供了如下导出方式。

5.2 导出
有两种方法能够导出当前模块中的内容。一种能够用关键字export标记声明。

默认导出的“操做数”是一个表达式(包括函数表达式和类表达式)。

另外一种,您能够在模块末尾列出您想导出的全部内容(这在风格上再次与显示模块模式相似)。

你也能够导出不一样的名称:
请注意,您不能使用保留字(如 default 和 new )做为变量名,但您可使用它们做为出口的名字(您也可使用它们做为属性名称在ECMAScript 5)。若是你想直接导入这样的命名导出,你必须为他们重命名。
5.3 从新导出
从新导出意味着将另外一个模块的导出添加到当前模块的导出中。您能够添加其余模块的全部导出:

或者你能够更有选择性(可选的重命名):

六、eval() 和 modules
eval()不支持模块语法。它根据脚本语法规则解析它的参数,脚本不支持模块语法(稍后解释缘由)。若是您想计算模块代码,您可使用模块加载器API(下面将介绍)。
注: (五、6两节里关于模块导入导出和循环依赖的知识点能够看一下我写的一篇相关文章。https://juejin.im/post/5c1e58326fb9a049a570e3d5)
七、ECMAScript 6模块加载器API
除了用于处理模块的声明性语法以外,还有一个编程API。它容许你:
- 以编程方式处理模块和脚本
- 配置加载模块
加载器处理解析模块说明符(import…from末尾的字符串id)、加载模块等。它们的构造函数是Reflect.Loader。每一个平台在全局变量系统(系统加载器)中保存一个定制的实例,该实例实现其特定的模块加载样式
7.1 导入模块和加载脚本

System.import()使您可以:
- 按需加载加载模块
import()检索单个模块,可使用Promise.all()导入多个模块

其余的加载方法:
7.2 配置加载模块
模块加载器API具备用于配置的各类钩子。这项工做仍在进行中。浏览器的第一个系统加载程序目前正在实现和测试中。其目标是找出如何最好地使模块加载可配置。
加载器API将容许对加载过程进行许多定制。例如:
一、导入的Lint模块(例如,经过JSLint或JSHint)。
二、在导入时自动转换模块(它们能够包含CoffeeScript或TypeScript代码)。
三、使用遗留模块(AMD, Node.js)。
可配置模块加载是node.js和CommonJS都有所限制的领域。
8.进一步的信息(这里是其余的一些延伸,感兴趣的能够去看看做者的其余文章)
如下内容回答了两个与ECMAScript 6模块相关的重要问题:我今天如何使用它们?如何在HTML中嵌入它们?
- 在HTML中嵌入ES6模块:<script>元素中的代码不支持模块语法,由于元素的同步特性与模块的异步性不兼容。相反,您须要使用新的<module>元素。在
ECMAScript 6 modules in future browsers这篇博客里,做者有解释 <module> 的工做原理。它有几个显著的优点,而且能够在其替代版本<script type="module">中进行填充。
9.ECMAScript 6模块的好处
乍一看,将模块构建到ECMAScript 6中彷佛是一个无聊的特性——毕竟,咱们已经有了几个很好的模块系统。可是ECMAScript 6模块有一些您没法经过库添加的特性,好比很是紧凑的语法和静态模块结构(这有助于优化、静态检查等)。他们也将——但愿——结束目前占主导地位的CommonJS和AMD之间的分裂。
对模块有一个单一的本地标准意味着:
- 新的浏览器api成为模块,而不是导航器的全局变量或属性
- 再也不将对象做为名称空间:在ECMAScript 5中,Math和JSON等对象用做函数的名称空间。未来,这些功能能够经过模块提供。
出自:http://2ality.com/2014/09/es6-modules-final.html#eval-and-modules(2ality – JavaScript and more)