Dojo1.6新特性:AMD规范

1. AMD的由来

前端技术虽然在不断发展之中,却一直没有质的飞跃。除了已有的各大著名框架,好比Dojo,JQuery,ExtJs等等,不少公司也都有着本身的 前端开发框架。这些框架的使用效率以及开发质量在很大程度上都取决于开发者对其的熟悉程度,以及对JavaScript的熟悉程度,这也是为何不少公司 的技术带头人都喜欢开发一个本身的框架。开发一个本身会用的框架并不难,但开发一个你们都喜欢的框架却很难。从一个框架迁移到一个新的框架,开发者颇有可 能还会按照原有框架的思惟去思考和解决问题。这其中的一个重要缘由就是JavaScript自己的灵活性:框架没办法绝对的约束你的行为,一件事情总能够 用多种途径去实现,因此咱们只能在方法学上去引导正确的实施方法。庆幸的是,在这个层面上的软件方法学研究,一直有人在去不断的尝试和改 进,CommonJS就是其中的一个重要组织。他们提出了许多新的JavaScript架构方案和标准,但愿能为前端开发提供银蛋,提供统一的指引。 html

AMD规范就 是其中比较著名一个,全称是Asynchronous Module Definition,即异步模块加载机制。从它的规范描述页面看,AMD很短也很简单,但它却完整描述了模块的定义,依赖关系,引用关系以及加载机制。 从它被requireJS,NodeJs,Dojo,JQuery使用也能够看出它具备很大的价值,没错,JQuery近期也采用了AMD规范。在这篇文 章中,咱们就将介绍AMD的性质,用法,优点以及应用场景。从AMD中咱们也能学习到如何在更高层面去设计本身的前端应用。 前端

2. AMD是什么

做为一个规范,只需定义其语法API,而不关心其实现。AMD规范简单到只有一个API,即define函数: 数组

define([module-name?], [array-of-dependencies?], [module-factory-or-object]); 浏览器

其中: 服务器

  • module-name: 模块标识,能够省略。
  • array-of-dependencies: 所依赖的模块,能够省略。
  • module-factory-or-object: 模块的实现,或者一个JavaScript对象。

从中能够看到,第一个参数和第二个参数都是能够省略的,第三个参数则是模块的具体实现自己。后面将介绍在不一样的应用场景下,他们会使用不一样的参数组合。 架构

从这个define函数AMD中的A:Asynchronous,咱们也不难想到define函数具备的另一个性质,异步性。当define函数 执行时,它首先会异步的去调用第二个参数中列出的依赖模块,当全部的模块被载入完成以后,若是第三个参数是一个回调函数则执行,而后告诉系统模块可用,也 就通知了依赖于本身的模块本身已经可用。若是对应到dojo1.6以前的实现,那么在功能上能够有以下对应关系: 框架

  • module-name: dojo.provide
  • dependencies: dojo.require
  • module-factory: dojo.declare

不一样的是,在加载依赖项时,AMD用的是异步,而dojo.require是同步。异步和同步的区别显而易见,前者不会阻塞浏览器,有更好的性能和灵活性。而对于NodeJs这样的服务器端AMD,则模块载入无需阻塞服务器进程,一样提升了性能。 异步

3. AMD实例:如何定义一个模块

下面代码定义了一个alpha模块,而且依赖于内置的require,exports模块,以及外部的beta模块。能够看到,第三个参数是回调函数,能够直接使用依赖的模块,他们按依赖声明顺序做为参数提供给回调函数。 ide

这里的require函数让你可以随时去依赖一个模块,即取得模块的引用,从而即便模块没有做为参数定义,也可以被使用;exports是定义的 alpha 模块的实体,在其上定义的任何属性和方法也就是alpha模块的属性和方法。经过exports.verb = ...就是为alpha模块定义了一个verb方法。例子中是简单调用了模块beta的verb方法。 函数

define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
    exports.verb = function() {
        return beta.verb();
        //或者:
        return require("beta").verb();
    }
});

4. 匿名模块

define 方法容许你省略第一个参数,这样就定义了一个匿名模块,这时候模块文件的文件名就是模块标识。若是这个模块文件放在a.js中,那么a就是模块名。能够在 依赖项中用"a"来依赖于这个匿名模块。这带来一个好处,就是模块是高度可重用的。你拿来一个匿名模块,随便放在一个位置就可使用它,模块名就是它的文 件路径。这也很好的符合了DRY(Don't Repeat Yourself)原则。

下面的代码就定义了一个依赖于alpha模块的匿名模块:

define(["alpha"], function (alpha) {
    return {
      verb: function(){
        return alpha.verb() + 2;
      }
    };
});

5. 仅有一个参数的define

前面提到,define的前两个参数都是能够省略的。第三个参数有两种状况,一种是一个JavaScript对象,另外一种是一个函数。

若是是一个对象,那么它多是一个包含方法具备功能的一个对象;也有多是仅提供数据。后者和JSON-P很是相似,所以AMD也能够认为包含了一 个完整的 JSON-P实现。模块演变为一个简单的数据对象,这样的数据对象是高度可用的,并且由于是静态对象,它也是CDN友好的,能够提升JSON-P的性能。 考虑一个提供中国省市对应关系的JavaScript对象,若是以传统JSON-P的形式提供给客户端,它必须提供一个callback函数名,根据这个 函数名动态生成返回数据,这使得标准JSON-P数据必定不是CDN友好的。但若是用AMD,这个数据文件就是以下的形式:

define({  
    provinces: [
    {
        name: '上海', 
        areas: ['浦东新区', '徐汇区']},
    {
        name: '江苏', 
        cities: ['南京', '南通']}
        //.....  
    ]
});

假设这个文件名为china.js,那么若是某个模块须要这个数据,只须要:

define(['china', function(china){
    //在这里使用中国省市数据
});

经过这种方式,这个模块是真正高度可复用的,不管是用远程的,仍是Copy到本地项目,都节约了开发时间和维护时间。

若是参数是一个函数,其用途之一是快速开发实现。适用于较小型的应用,你无需提早关注本身须要什么模块,本身给谁用。在函数中,能够随时require本身须要的模块。例如:

define(function(){
    var p = require('china');
    //使用china这个模块
});

即你省略了模块名,以及本身须要依赖的模块。这不意味着你无需依赖于其余模块,而是可让你在须要的时候去require这些模块。define方 法在执行的时候,会调用函数的toString方法,并扫描其中的require调用,提早帮助你载入这些模块,载入完成以后再执行。这使得快速开发成为 可能。须要注意的一点是,Opera不能很好的支持函数的toString方法,所以,在浏览器中它的适用性并非很强。但若是你是经过build工具打 包全部的 JavaScript文件,这将不是问题,构建工具会帮助你扫描require并强制载入依赖的模块。

6. Dojo中的AMD

Dojo 在三月初正式发布了1.6版本,其中一个重要的变化就是引入了AMD机制,取代了原来的dojo.provide和dojo.require方法。可是现 在仍然保持了向后兼容性,你仍然能够用dojo.provide和dojo.require来定义和加载模块。须要注意的是:在 Dojo 1.6 中, 针对 AMD 的重构仍然属于一个过渡期的改动 , 用户本身开发的 AMD 模块还不能被 Dojo 的加载器和 Build 系统支持 . 1.6 中现有的编译系统对AMD的支持还很是局限。 若是你本身开发了 AMD 格式的模块,而且你仍然在使用默认的 Dojo 同步模块加载器,那么你必须严格遵循 Dojo 模块的格式 ( 包括换行的格式 ) 来保证你本身的模块可以成功编译。总结起来有如下三点:

  • 用传统的方法 (dojo.require()/dojo.provide()) – 这些模块,只能被 Dojo 同步加载器 加载,但能够被 Dojo 编译系统(Build System )正确的编译
  • 用 Dojo 同步加载器来加载 AMD 格式 ( define ()) 模块 – 这些模块能够被正常的加载,而且能够被其余兼容 AMD 格式的加载器加载 . 如今虽然 Dojo1.6 尚未正式支持这种用法, 但在目前的 Dojo1.6 编译系统中,是能够正常工做的 . ( 前提是你必须严格遵循 Dojo 模块定义的代码规范 )
  • 使用第三方加载器来加载 AMD 格式( define ())模块 – 模块能够被正常加载,而且能够被其余加载器所使用 . 这些模块可使用 RequireJS 或 Backdraft 提供的编译系统正常编译,可是 Dojo 尚未正式的测试过和其余加载器的兼容性 .

以Calendar为例,用define方法来定义这个模块:

define("dijit/Calendar", 
    ["dojo", "dijit", "text!dijit/templates/Calendar.html", 
	"dojo/cldr/supplemental", "dojo/date", "dojo/date/locale",
	"dijit/_Widget", "dijit/_Templated", "dijit/_CssStateMixin", "dijit/form/DropDownButton"], 
	function(dojo, dijit) { 
        dojo.declare(
            "dijit.Calendar",
            [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
            {...}
        );
        return dijit.Calendar;
    }
);

能够看到,模块标识就是模块文件的路径,模块自己通常都是dojo.declare定义的类。Dojo1.6中的dojo和dijit命名空间下的 模块均已经用AMD的形式进行了重构,但dojox下仍然延用了传统的dojo.provide和dojo.require形式。对AMD的引入是 Dojo走向自动化包管理的重要一步,在后续文章中咱们也将继续关注Dojo在这方面的进展。

7. 结论

AMD 规范是JavaScript开发的一次重要尝试,它以简单而优雅的方式统一了JavaScript的模块定义和加载机制,并迅速获得不少框架的承认和采 纳。这对开发人员来讲是一个好消息,经过AMD咱们下降了学习和使用各类框架的门槛,可以以一种统一的方式去定义和使用模块,提升开发效率,下降了应用维 护成本。

参考资料:

Dojo 中文博客
RequireJS/AMD Module Forms
Modules/AsynchronousDefinition

相关文章
相关标签/搜索