前端模块化(四):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模块加载不是同步的,因此在浏览器下不会发生阻塞的情况。
3 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规范的具体实现。