16.8 ES6模块的设计目标
了解以下的几条设计目标有助于理解ES6模块为什么这么定义:
- 更青睐export default的方式
- 静态模块结构
- 支持同步与异步的加载
- 支持模块间的循环依赖
16.8.1 更青睐export default的方式
这种语法暗示:采用export default方式导出的那个对象才是模块,而不包括export ...的方式;你可能感觉奇怪,实际上这是为了让导出尽可能的方便直观。引用David Herman的描述:
ES6更青睐于导出单个/默认(即:export default)的方式,这种语法很直观,采用多个导出(即:export ...)的方式反而有点不简洁。
16.8.2 静态模块结构
在ES6出现之前的模块结构是动态的:导出和导入的模块是在运行时才能确定,这也是我们为什么在ES6中引入静态结构,深入之前,先说明一下什么是静态结构。
这意味着你在编译期就可以知道导入与导出的模块名称(静态方式),仅需看下源代码,无需执行JS才能清晰依赖关系。并且ES6还加强了这种语法的限制:只能在顶层结构使用import和export(不能使用在if等的声明块中),同时import与export不能拥有动态部分(如:变量等)。
下面的两个例子均不拥有静态结构:
````
var my_lib;
if (Math.random()) {
my_lib = require('foo');
} else {
my_lib = require('bar');
}
````
if (Math.random()) {
exports.baz = ···;
}
虽然ES6模块强制静态结构,缺少了灵活性,但是却能得到以下的好处:
16.8.2.1 好处:打包时消除dead code elimination
前端开发中,各模块通常会如下处理:
- 开发阶段,代码以很多小模块的形式存在;
- 部署阶段,模块打包在少数几个大的文件中;
这么做的原因有三条:
- 更少的文件去包含所有的依赖模块;
- 压缩打包后的文件比很多个小文件更有效;
- 打包期间,没有被用到的export可以去除掉,减小文件体积;
16.8.2.2 好处:更紧凑的打包方式,无指定格式
有款Rollup的模块打包工具,已经证明了采用ES6模块方式的代码可以有效的将代码合并,他们可以处于同一个作用域下(经过重命名消除冲突后)。
参考下面的例子:
````
// lib.js
export function foo() {}
export function bar() {}
// main.js
import {foo} from './lib.js';
console.log(foo());
Roolup可以将这两个模块成一个文件,并且消除不会用到的export。
````
function foo() {}
console.log(foo());
这种方式的另外一个好处是,无指定格式,打包后仍是一个ES6模块。
16.8.2.3 好处:依赖查找更快
以下代码采用CommonJS的方式,
````
var lib = require('lib');
lib.someFunc(); // property lookup
因为是动态结构,查找起来会慢很多,并且有时运行时才能确定。相反,采用ES6模块的方式,可以很快速的通过代码找到出处,并且优化。
````
import * as lib from 'lib';
lib.someFunc(); // statically resolved
16.8.2.4 好处:变量检查
通过静态结构,你总能静态分析出任何文件中一个变量的来源:
- 全局变量:逐渐地,全局变量将仅来自于语言层面,其它的将逐渐转移到模块中(包括标准库与浏览器提供的一些功能)。
- 模块引入的变量
- 模块内部的变量
这将极大的帮助检查是否代码中有拼写错误,如:JSHint,JSLint。ES6中,JS引擎可以发现出来。
16.8.2.5 好处:使用宏定义
Javascript将来也会把宏归纳进来,如果JS引擎支持宏,你就可以通过类库加入一种新的语法。Sweet.js是一种尝试性的宏定义类库,以下来自于它的官网,通过宏定义类:
````
// Define the macro
macro class {
rule {
$className {
constructor $cparams $cbody
$($mname $mparams $mbody) ...
}
} => {
function $className $cparams $cbody
$($className.prototype.$mname
= function $mname $mparams $mbody; ) ...
}
}
// Use the macro
class Person {
constructor(name) {
this.name = name;
}
say(msg) {
console.log(this.name + " says: " + msg);
}
}
var bob = new Person("Bob");
bob.say("Macros are sweet!");
16.8.2.6 好处:为类型做准备
静态类型检查会把约束加到宏定义上,如果拥有静态结构,那么在引入模块时也可以得到它的类型信息。
16.8.2.7 好处:支持其它语言
如果你想把用宏或者静态类型定义的语言转换为JS,那么模块需要拥有静态结构,方便分析,否则动态结构更不好控制。
16.8.2.8 好处:参见出处
- David Herman的静态模块解决方案
16.8.3 支持同步与异步的加载
ES6模块定义应该与打包工具等是采用同步还是异步加载的方式独立开来,它的语法很好的适用于同步加载,针对异步加载,可以在执行之前全部将静态分析得到的资源加载进来(以过去AMD的方式)。
16.8.4 支持模块间的循环依赖
译者注:ES6模块中的循环依赖于CommonJS的循环依赖解决是不一样的。
参考资料: