使用AMD,CommonJS和ES Harmony编写模块化JavaScript代码(ES Harmony)

原文:https://addyosmani.com/writing-modular-js/

ES Harmony 模块化的未来


TC39,是负责定义ECMAScript语法和语义标准的机构,由一群精英的开发人员组成,不断进行标准规范的版本迭代。在过去几年里,一些开发人员(像Alex Russell)已经将探索的重心放在让JavaScript适合大型项目开发的解决方案上,并且也深深的体会到对模块化JS编程的强烈需求。

因此,已经提出了一些针对这一需求的改进方案。其中包括可以适用于客户端和服务端的灵活的模块系统模块加载器以及一些其他的特性。在这一章里,我将会针对这些即将发布的新特性,展示对应的示例代码,期望能够让你对其有最基本的了解。

注意:虽然Harmony还是在提案阶段(译者注:请以当前最新的ES规范为准),但是多亏了Chrome的Traceur 编译器,你已经可以去尝试这些新的功能特性了。通过入门教程,可以在几分钟内上手。如果你对这个项目十分感兴趣的话,那么可以尝试阅读JSConf描述(译者注:原文中存在链接,但打开后404)。

具有imports和exports的模块(Modules)

如果你已经读过了AMD和CJS模块标准的话,那么应该熟悉了imports(module dependencies 模块依赖)和exports(或者是可被其他模块访问的公共API/变量)的的概念。在下一版的ES规范(ES.next)中,上述依赖概念被简洁地定义为import关键字来实现。export和我们期待的并没有什么不同,我想大多数的开发者通过下面的示例,就能很快的理解。

  • import声明,将模块的exports作为局部变量进行绑定,并且可能会通过重命名以避免变量的冲突。
  • export声明,将本地绑定的模块声明为外部可见的,因此其他的模块可以访问该模块的exports,但不能修改。有趣的是,模块可以暴露(export)它的子模块,而不能暴露在其他位置定义的模块。同时,可以将暴露的方法或变量重命名,而无需使用其本地的名称。
module staff{
    // specify (public) exports that can be consumed by
    // other modules
    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{

    // specify dependencies
    import baker from staff;

    // import everything with wildcards
    import * from skills;

    export var oven = {
        makeCupcake: function( toppings ){
            baker.bake('cupcake', toppings);
        },
        makeMuffin: function( mSize ){
            baker.bake('muffin', size);
        }
    }
}

从远程资源加载模块

ES Harmony的模块化协议同样适用于基于远程的模块(例如,第三方的API封装),使其能够简洁的从外部位置加载模块。下面就是远程拉取我们之前定义的模块并且使用的例子

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');
    });

服务端类似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{

    // We can define the body of a class' constructor
    // function by using the keyword 'constructor' followed
    // by an argument list of public and private declarations.
    constructor( name, toppings, price, cakeSize ){
        public name = name;
        public cakeSize = cakeSize;
        public toppings = toppings;
        private price = price;

    }

    // As a part of ES.next's efforts to decrease the unnecessary
    // use of 'function' for everything, you'll notice that it's
    // dropped for cases such as the following. Here an identifier
    // followed by an argument list and a body defines a new method

    addTopping( topping ){
        public(this).toppings.push(topping);
    }

    // Getters can be defined by declaring get before
    // an identifier/method name and a curly body.
    get allToppings(){
        return public(this).toppings;
    }

    get qualifiesForDiscount(){
        return private(this).price > 5;
    }

    // Similar to getters, setters can be defined by using
    // the 'set' keyword before an identifier
    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总结

像你看到的那样,即将到来的ES.next会伴随这许多令人兴奋的特性。虽然在现在可以使用Traceur来尝试新的特性,但是立刻将Harmony使用到当前的项目中,并非是一个优越的选择(就目前来看)。这其中仍然有些问题存在,例如官方规范的变动,以及在跨浏览器兼容性可能存在的问题(例如IE会在运行ES Harmony一段时间后锁死进程)。所以最好等到规范最终的确定,并且囊括了AMD(浏览器模块)和CJS(服务端模块)。

相关阅读
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代码的现代化模块规范。这些模块相较于传统的模块模式具有很大优势:包括避免了开发人员针对每个创建的模块去定义全局变量,更好的支持动态和静态的依赖管理,通过脚本加载器提高可配型,在服务端使模块具有更优的可配行,等等。

简而言之,我建议尝试下当前被推荐的这些规范,它们为构建可复用的模块化功能提供了更强的效率和灵活性。

这就是全部内容,如果你有任何疑问关于这些主题,欢迎在twitter上与我联系,我将尽我的最大努力回答!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章