前端模块化(四):AMD规范


1 概述

AMD(Asynchronous Module Definition),也就是异步模块定义。AMD规范,制定了定义模块的规则,使得模块之间的依赖可以被异步加载。这和浏览器的异步加载模块的环境刚好适应(浏览器同步加载模块会导致性能、可用性、调试和跨域访问等问题)。

所谓异步,就是所有的模块将被异步加载,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。这就是浏览器端模块加载器核心所在。

AMD规范由CommonJs规范演进而来,大部分思想跟CommonJS类似,属于Modules/Async流派。但AMD规范是专注于浏览器端的,根据浏览器特点做了自己的一些定义实现。下面我们来了解一下AMD规范。

2 define方法:定义模块

AMD规范定义了一个自由变量或者说是全局变量 define 的函数,其作用是用来定义并暴露一个模块,函数代码如下:

define(id?, dependencies?, factory);

l  第一个参数,id(名字),是个字符串。它指的是定义中模块的名字,这个参数是可选的。如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。

l  第二个参数,dependencies(依赖),是个定义中模块所依赖模块的数组。依赖模块必须根据模块的工厂方法优先级执行,并且执行的结果应该按照依赖数组中的位置顺序以参数的形式传入(定义中模块的)工厂方法中。

l  第三个参数,factory(工厂方法),为模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。

2.1  define.amd 属性

为了清晰的标识全局函数(为浏览器加载script必须的)遵从AMD编程接口,任何全局函数应该有一个"amd"的属性,它的值为一个对象。这样可以防止与现有的定义了define函数但不遵从AMD编程接口的代码相冲突。

判断是否存在define.amd属性,可判断当前的文件加载是否遵循AMD规范。目前我们大部分的主流的前端库都做了AMD兼容,判断是否存在define.amd属性,然后执行对应的加载模式。例如jQuery最后几行的写法如下:

if ( typeof define === "function" && define.amd ) {

    define( "jquery", [], function () { return jQuery; } );

}

2 require方法:加载模块

require方法用于调用模块,但是不同于CommonJS,它有两个参数,与define方法类似。结构代码如下:

require([module], callback);

第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:

require(['a'], function (a) {

    a.FuncA();

  });

上面方法表示加载a模块,当这个模块加载成功后,执行一个回调函数。该回调函数就用来完成具体的任务。a.FuncA()与a模块加载不是同步的,因此在浏览器下不会发生阻塞的情况。

AMD实例

3.1 普通模块

define("a", ["require", "exports", "b"], function (require, exports, b) {

       exports.funb = function() {

           return b.funb();

           //Or:

           return require("b").funb();

       }

   });

上面例子,创建一个名为"a"的模块,使用了require,exports,和名为"b"的模块。第三个参数是回调函数,可以直接使用依赖的模块,他们按依赖声明顺序作为参数提供给回调函数。

3.2 匿名模块

define(["a"], function (a) {

       return {

         funb: function(){

           return a.funb();

         }

       };

   });

上面的例子中,我们忽略了define 方法的第一个参数,这样就定义了一个匿名模块,而模块文件的文件名就是模块标识。例如,上面定义的这个模块文件放在b.js中,那么b就是它的模块名。如果其他模块想引用这个模块的话,可以在依赖项中用"b"来加载这个匿名模块。

3.3 独立模块

define({

     funA: function(a, b){

       return a + b;

     }

   }); 

如果被定义的模块是一个独立模块,不需要依赖任何其他模块,可以直接用define方法生成。上面的例子中,定义了一个拥有funA方法的模块。

3.4   非独立模块

define(function (require, exports, module) {

     var a = require('a'),

         b = require('b');
 

     exports.fun = function () {

             a.funA();

             b.funB();

         };

   });

上面例子中,定义的模块返回一个对象,该对象的fun方法就是外部调用的接口。这种方法与 CommonJS 方式定义有些类似。需要注意的是,回调函数必须返回一个对象,这个对象就是你定义的模块。

下一篇我们开始介绍AMD规范的产物RequireJs加载器,进一步了解AMD规范的具体实现。

相关