原文:https://addyosmani.com/resources/essentialjsdesignpatterns/book/#detailharmony
未来的模块
TC39 是负责定义 ECMAScript 语法和语义的及其未来版本的标准机构,它是由许多非常聪明的开发者组成。其中的一些人(如 Alex Russell)在过去几年中一直密切关注 JavaScript 在大规模开发中用法的演变,并且强烈的意识到需要更好的语言特性来编写更模块化的 JS。
为此,当前已经有一些令人激动的语言补充提案,其中就包括了灵活模块(它可以同时用在客户端和服务端)、模块加载器及其他。在本节中,我们将探索使用 ES.next 中提出模块语法的代码示例,这样我们就可以尝尝未来的味道。
注意: 尽管 Harmony 仍在提案阶段,得益于谷歌的 Traceur 编译器, 我们已经可以尝试(部分)ES.next 关于编写模块化 JavaScript 的特性。阅读这篇 新手指南 ,你就可以快速的上手 Traceur。如果想了解这个项目更多的信息,还可以查看这篇 JSConf 的演讲。
拥有 Imports 和 Exports 的模块
读完了关于 AMD 和 CommonJS 模块的章节,你可能已经熟悉了模块依赖(imports)和模块导出(或者供其它模块使用的公开 API/变量)。在 ES.next 中,这些概念更加简明,依赖的管理通过 import
关键字来指定。 export
与我们期望的并没有太大的区别,很多开发者看完下面的代码示例后立马就学会了。
- import 声明表达式会绑定模块的导出到本地的变量,并且可以被重命名以避免命名冲突。
- export 声明表达式用于声明模块导出部分的本地绑定关系,外部的模块可以通过 export 读取到它,但是无法修改它们。有趣的是,模块可以导出子模块,但是不能导出已经定义在其他地方的模块。我们还可以重命名导出,使得它们外部的命名与本地的命名不同。
module staff {
// 指定(公开)可以被其他模块使用的导出
export var baker = {
bake: function(item){
console.log('Woo! I just baked ' + item);
}
}
}
module skills {
export var specialty = 'baking';
export var experience = '5 years';
}
module cakeFactory {
// 指定依赖
import baker from staff;
// 使用通配符获取导出所有内容
import * from skills;
export var oven = {
makeCupcake: function(toppings){
baker.bake('cupcake', toppings);
},
makeMuffin: function(mSize){
baker.bake('muffin', size);
}
}
}
从远程源加载的模块
模块的提案同时还提供了适用位于远程的模块(如,第三方库)的方案,让从外部加载模块变得更简单。下面的例子是引入上面定义好的模块并使用它。
module cakeFactory from 'http://addyosmani.com/factory/cakes.js';
cakeFactory.oven.makeCupcake('sprinkles');
cakeFactory.oven.makeMuffin('large');
模块加载器 API
模块加载器提案描述了一个用于在高度可控的环境中加载模块的动态 API。加载器支持的签名有:用于加载模块的 load(url, moduleInstance, error)
, createModule(object, globalModuleReferences)
和 其他。
这里是另一个关于在我们预先定义的模块中的动态加载的示例。注意,这里与上一个从远程源拉取模块的示例不同,模块加载器 API 更适合动态的环境。
Loader.load('http://addyosmani.com/factory/cakes.js', function(cakeFactory) {
cakeFactory.oven.makeCupcake('chocolate');
});
用于 Server 端的类 CommonJS 模块
对于对服务端环境更感兴趣的开发者,ES.next 提案的模块系统并不是只关注了浏览器环境的模块系统。下面的示例中,我们可以看到一个用于在服务端使用的类 CommonJS 模块。
// io/File.js
export function open( path ) { ... };
export function close( hnd ) { ... };
// compiler/LexicalHandler.js
module file from 'io/File';
import { open, close } from file;
export function scan(in) {
try {
var h = open(in) ...
}
finally { close(h) }
}
module lexer from 'compiler/LexicalHandler';
module stdlib from '@std';
//... scan(cmdline[0]) ...
含构造器、Getter 和 Setter 的类
对于纯粹注意而言,类的概念一直是有争议的问题。到目前为止,我们要么是依赖JavaScript的原型特性,要么是借助框架或者抽象概念来提供使用 类 的定义。
Harmony 的提案为语言提供了拥有构造器和真私有概念的类。在下面的示例中,行内注释解释了类是如何被构造的。
读完下面的代码后,可能会有人注意到了这里没有 “function” 这个关键词。这并不是一个打印错误:TC39 一直在有意识地尝试减少我们对 function
关键字的滥用,希望这能有助于我们简化我们编写代码的方式。
class Cake{
// 我们可以通过关键字 "constructor" 来定义类构造器的主体,
// 接在后面的事一个公有和私有声明的参数列表
constructor(name, toppings, price, cakeSize) {
public name = name;
public cakeSize = cakeSize;
public toppings = toppings;
private price = price;
}
Here an identifier
// 作为 ES.next 对减少非必要的 "function" 的努力,
// 你会注意到在这种情况下 "function" 被删除了。
// 这里的新方法的定义是标识后紧接着参数列表和方法主体。
addTopping(topping) {
public(this).toppings.push(topping);
}
// getter 是通过在变量/方法名前声明一个 "get" 关键字
get allToppings() {
return public(this).toppings;
}
get qualifiesForDiscount() {
return private(this).price > 5;
}
// 与 getter 类似,setter 是通过在标识前声明 "set" 关键字
set cakeSize(cSize) {
if(cSize < 0) {
throw new Error('Cake must be a valid size - either small, medium or large');
}
public(this).cakeSize = cSize;
}
}
ES Harmony 总结
正如我们所看到的,Harmony 可能会带来一些令人激动的新特性,它们会简化模块化应用的开发及处理像依赖管理之类的问题。
目前,我们在今天的浏览器中使用 Harmony 语法最好的选择是通过 Google Traceur 或者 Esprima 这样的编译器。还有像 Require HM 这样的项目可以让我们在 AMD 中使用 Harmony 模块。但是在规范最终完成前,我们最好选择 AMD(针对浏览器端模块)和 CommonJS(针对服务端)。
相关阅读
- A First Look At The Upcoming JavaScript Modules
- David Herman On JavaScript/ES.Next (Video)
- ES Harmony Module Proposals
- ES Harmony Module Semantics/Structure Rationale
- ES Harmony Class Proposals
总结
在本节中,我们回顾了几种使用现代模块格式编写模块化 JavaScript 的方案。
与单独使用模块模式相比,这些格式有一些优点:不需要管理全局变量、更好的支持静/动态依赖管理、提升与脚本加载器的兼容性、更好的兼容了服务端的模块等等。
总的来说,我推荐尝试一下本章中的建议,因为这三种格式提供了强大的功能和灵活性,能帮助我们更好的组织我们的应用。