全面解析ECMAScript 5和ECMAScript 6模块系

作者: fullbook | 来源:发表于2016-08-18 14:57 被阅读299次

    ECMAScript 5模块系统

    1、CommonJS模块系统(require和export)

    CommonJs是一个由开源开发者组成的团队,主要围绕JavaScript实现一些API及开展研发实践。该团队提出了一个JavaScript模块规范。每个文件都可当作一个模块,并且每个文件可以访问两个对象:require和export。require用来接收字符串(模块名),并返回该模块输出的对象。export对象用来导出该模块的方法和变量。require方法返回的就是export对象。模块同步加载。服务器端JavaScript引擎Node.js就是用的这个模块系统。

    2、AMD异步模块定义(define)

    AMD是一个采用异步方式加载依赖模块的模块系统。如果模块在不同文件中,它们将采用XHR进行加载。某一模块将等其所依赖的模块一一加载后才会被执行。AMD模块必须是一个函数,并作为参数传入define函数中。函数的返回值将传输给所有依赖的模块,所获得返回值又将作为参数传给模块方法。Require.js库中实现了AMD。

    3、TypeScript同步加载模块(export module、import)

    TypeScript,作为JavaScript的超集,也提供了一个模块系统。当它被编译时,便开始使用JavaScript模块模式。TypeScript模块使用module关键字定义,任何被输出的对象必须使用export关键字定义。import关键字用来将其它模块加载入模块中,并捕捉该模块导出的对象。TypeScript模块是同步加载的。

    ES6模块系统

    ES6模块系统启发于上述现有模块系统,它具有以下特性:
    1、使用export关键词导出对象。这个关键字可以无限次使用;
    2、使用import关键字将其它模块导入某一模块中。它可用来导入任意数量的模块;
    3、支持模块的异步加载;
    4、为加载模块提供编程支持。
    接下来让我们通过具体编程方法看看每一个特性。
    导出对象
    在现有的模块系统中,每个JavaScript代码文件在ES6中都是一个模块。只有模块中的对象需要被外部调用时,模块才会输出对象,其余则都是模块的私有对象。该处理方式将细节进行封装,仅导出必要的功能。
    从模块里导出对象,ES6为我们提供了不同方法,见下面的讨论。
    内联导出
    ES6模块里的对象可在创建它们的声明中导出。一个模块中可无数次使用export,所有的对象将被一起导出。请看下面的例子:
    <pre>export class Employee {
    constructor(id, name, dob){
    this.id = id;
    this.name=name;
    this.dob= dob;
    }
    getAge(){
    return (new Date()).getYear() - this.dob.getYear();
    }
    }
    export function getEmployee(id, name, dob) {
    return new Employee(id, name, dob);
    }
    var emp = new Employee(1, "Rina", new Date(1994, 9, 15)); </pre>

    案例中的模块导出了两个对象: Employee类,getEmployee函数。因对象emp未被导出,所以其仍为模块私有。
    导出一组对象
    尽管内联导出很有效,但在大规模模块中,它就很难发挥作用了,因为我们可能无法追踪到模块导出来的对象。在这种情况下,更好的办法是,在模块的末尾单独进行导出声明,以导出该模块中的全部对象。
    使用单独导出声明重写上一案例中的模块,结果如下:
    <pre>class Employee {
    constructor(id, name, dob){
    this.id = id;
    this.name=name;
    this.dob= dob;
    }
    getAge(){
    return (new Date()).getYear() - this.dob.getYear();
    }
    }
    function getEmployee(id, name, dob) {
    return new Employee(id, name, dob);
    }
    var x = new Employee(1, "Rina", new Date(1994, 9, 15));
    export {Employee, getEmployee};</pre>
    在导出时,重命名对象也是可以的。如下例所示,Employee在导出时名字改为了Associate,函数GetEmployee改名为getAssociate。
    <pre>export {
    Associate as Employee,
    getAssociate as getEmployee
    }; </pre>
    Default导出
    使用关键字default,可将对象标注为default对象导出。default关键字在每一个模块中只能使用一次。它既可以用于内联导出,也可以用于一组对象导出声明中。
    下面案例展示了在组导出语句中使用default:
    <pre>export default {
    Employee,
    getEmployee
    }; </pre>
    导入模块
    现有模块可以使用关键字import导入到其它模块。一个模块可以被导入任意数量的模块中。下文展示了导入模块的不同方式。
    无对象导入
    如果模块包含一些逻辑要执行,且不会导出任何对象,此类对象也可以被导入到另一模块中。如下面案例所示:
    <pre>import './module1.js';</pre>
    ** 导入默认对象**
    采用Default导出方式导出对象,该对象在import声明中将直接被分配给某个引用,如下例中的“d”。
    <pre>import d from './module1.js'; </pre>
    ** 导入命名的对象**
    正如以上讨论的,一个模块可以导出许多命名对象。如果另一模块想导入这些命名对象,需要在导入声明中一一列出这些对象。举个例子:
    <pre>import {Employee, getEmployee} from './module1.js'; </pre>
    当然也可在同一个声明中导入默认对象和命名对象。这种情况下,默认对象必须定义一个别名,如下例。
    <pre>import {default as d, Employee} from './module1.js'; </pre>

    ** 导入所有对象**
    以上几种情况,只有import声明中列举的对象才会被导入并被使用,而其它对象则无法在导入模块中使用。当然,这就要求用户了解哪些对象可以导出并加以利用。如果模块导出大量对象,另一模块想引入所有导出的对象,就必须使用如下声明:
    <pre>import * as allFromModule1 from './module1.js'; </pre>
    allFromModule1这一别名将指向所有从module1导出的对象。在导入模块中,它们作为属性可被访问。
    可编程式的按需导入
    如果想基于某些条件或等某个事件发生后再加载需要的模块,可通过使用加载模块的可编程API(programmatic API)来实现。使用System.import方法,可按程序设定加载模块。这是一个异步的方法,并返回Promise。
    该方法的语法示例如下:
    <pre>System.import('./module1.js')
    .then(function(module1){
    //use module1
    }, function(e){
    //handle error
    }); </pre>

    如果模块加载成功且将导出的模块成功传递给回调函数,Promise将会通过。如果模块名称有误或由于网络延迟等原因导致模块加载失败,Promise将会失败。
    结论
    任何一个大型应用中,模块化十分必要。ES6模块为JavaScript提供了该特性,这些模块提供了众多选择来导出和引入对象。我很期待该特性被浏览器支持的那一天,到时我们无需加载任何第三方库即可创建、加载JavaScript模块。目前流行的客户端MVC框架Angular.js在其2.0版本(目前还在开发中)中就使用了ES6的模块化。
    让我们开始使用模块系统,从而让我们的代码更具组织和可读性。

    相关文章

      网友评论

        本文标题:全面解析ECMAScript 5和ECMAScript 6模块系

        本文链接:https://www.haomeiwen.com/subject/ugyvsttx.html