ES6 module 解析
22 Jun 2016
Reading time ~1 minute
ES6 Module
随着应用系统复杂度不断提升,如何兼顾开发效率和产品实际运行效率是每个开发者都应该考虑的问题。一般的做法是,在开发阶段运用组件化和模块化的手段分离关注点,借助构建工具输出制定的产品。
主流的高级语言都提供了相应的模块化解决方案,但是 ES5
之前的 JavaScript
却一直没有模块体系。在诸多的第三方模块加载方案中,CommonJS
和 AMD
两种脱颖而出,成了默认的标准,前者用于服务器,后者用于浏览器。
ES6
设计模块时,在语言规格的层面上进行实现,以取代现有的 CommonJS
和 AMD
规范,成为浏览器和服务器通用的模块解决方案。
ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。比如,CommonJS模块就是对象,输入时必须查找对象属性。
// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于
let fs = require('fs');
let stat = fs.stat, exists = fs.exists, readfile = fs.readfile;
上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象( fs
),然后再从这个对象上面读取3个方法。这种加载称为”运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做”静态优化”。
ES6模块不是对象,而是通过export命令显式指定输出的代码,输入时也采用静态命令的形式。
// ES6模块
import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为”编译时加载”,即ES6可以在编译时就完成模块加载,效率要比CommonJS模块的加载方式高。当然,这也导致了没法引用ES6模块本身,因为它不是对象。
由于ES6模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽JavaScript的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
除了静态加载带来的各种好处,ES6模块还有以下好处。
- 不再需要UMD模块格式了,将来服务器和浏览器都会支持ES6模块格式。目前,通过各种工具库,其实已经做到了这一点。
- 将来浏览器的新API就能用模块格式提供,不再必要做成全局变量或者navigator对象的属性。
- 不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。
有一点需要注意,Node的默认模块格式是CommonJS,目前还没决定怎么支持ES6模块。所以,只能通过Babel这样的转码器,在Node里面使用ES6模块。
加载机制
ES6模块加载的机制,与CommonJS模块完全不同。CommonJS模块输出的是一个值的拷贝,而 ES6模块输出的是值的引用。
import
import { x } from 'y'
import命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块对外接口的名称相同。注意,{ x }
不是解构。
import 语法形式示例如下
Import Statement Form | [[ModuleRequest]] | [[ImportName]] | [[LocalName]] |
---|---|---|---|
import v from “mod”; | “mod” | “default” | “v” |
import * as ns from “mod”; | “mod” | “*” | “ns” |
import {x} from “mod”; | “mod” | “x” | “x” |
import {x as v} from “mod”; | “mod” | “x” | “v” |
import “mod”; | An ImportEntry Record is not created. |
export
export 语法形式示例如下
Export Statement Form | [[ExportName]] | [[ModuleRequest]] | [[ImportName]] | [[LocalName]] |
---|---|---|---|---|
export var v; | “v” | null | null | “v” |
export default function f(){}; | “default” | null | null | “f” |
export default function(){}; | “default” | null | null | “default” |
export default 42; | “default” | null | null | “default” |
export {x}; | “x” | null | null | “x” |
export {v as x}; | “x” | null | null | “v” |
export {x} from “mod”; | “x” | “mod” | “x” | null |
export {v as x} from “mod”; | “x” | “mod” | “v” | null |
export * from “mod”; | null | “mod” | “*” | null |
export default
export default命令用于指定模块的默认输出。本质上,export default是一种特殊的命名导出(named export),就是输出一个叫做 default
的变量或方法,然后系统允许你为它取任意名字。
export default «expression»;
等价于
const __default__ = «expression»;
export { __default__ as default };
以下两个模块有同样的默认导出。
//------ module1.js ------
export default function foo() {} // function declaration!
//------ module2.js ------
function foo() {}
export { foo as default };
import 的时候,下面两句是等效的。
import { default as foo } from 'lib';
import foo from 'lib';
参考资料
alcat2008
Dreamer, Practitioner, Incomplete Front-ender