JS的强大已经不用解释了,从Web RIA到Node服务器,到处都是JS的身影。然而由于出身的缘故,JS本身在大规模应用上存在着很多问题,比如模块化编程。本文以浏览器端模块化编程为内容,重点介绍AMD规范和CMD规范。在服务器端,NodeJS遵守的Common规范我们这里暂不讨论。
对于计算机语言,模块化编程是必不可少的,对架构设计、代码复用起到至关重要的作用,工程中引入别人写好的库和模块能大大缩减开发周期。C/C++中,我们可以用include;Java中我们可以用import。可是在JS中,这些是不存在的,取而代之的是script标签。
在浏览器端,我们可以通过script标签引入需要的库,然后加载自己的脚本,最后运行脚本。这样做似乎与include和import没什么区别,然而有几个重要的因素必须要考虑:JS是解释型语言,边加载边运行,后续脚本运行时,这些脚本所依赖的一切必须已经加载完毕;JS脚本加载时会阻塞浏览器,如果加载的JS很多很大,浏览器会卡住,带来很差的用户体验;通过调整script标签顺序可以修改JS模块之间的依赖,然而当模块很多时,这种做法就不可用了。
鉴于以上,JS模块化编程规范应运而生,并出现了大量实现。这些实现,简单讲就是一个超底层的JS库,这个库的作用有两个:一是完成JS脚本的异步加载与执行;二是确保JS脚本按照用户设计的依赖关系加载,即一段脚本执行时,它的所有依赖已经加载完毕了。不同的规范,只是写法用法不同而已,最终目的都是一样的。另外,模块化编程与异步加载不分家,会让JS按需加载,大大提高页面响应速度;模块加载提供命名空间的效果,有效防止作用域污染,并有一定的容错能力。
AMD规范和CMD规范是目前最为流行的两种模块化编程模式,符合AMD规范的库有RequireJS,符合CMD规范的库有SeaJS。
AMD翻译过来叫“异步的模块定义”,接口非常简单,只有一个define,完整格式如下:
define(‘module-name’, [‘lib1’, ‘lib2’], function (lib1, lib2) {
// todo
return {};
});
第一个参数是模块的名称,可以省略;第二个是模块的依赖,也就是其他模块,如果省略,则此模块没有任何依赖;第三个参数是依赖加载完毕后的回调函数,回调函数的形参是依赖模块的输出,顺序与第二个数组参数一致,回调函数的返回值就是此模块的输出,可以作为其他模块的依赖形参。
这种模块没有任何依赖,不做任何操作,只是为了提供数据源,可以这样定义:
define({
color: "black",
size: "unisize"
});
如果模块没有任何依赖,但进行了一些预处理,可以这样定义:
define(function() {
// TODO
return {
color: "black",
size: "unisize"
}
});
这种模块是最常见的,意为模块的输出是一个函数,模块的依赖是这个输出函数使用的外部变量,因此这种模块的输出是一个闭包。使用时也要格外小心,尤其是输出函数当做构造函数使用时。
define(["my/cart", "my/inventory"],
function(cart, inventory){
//return a function todefine "foo/title".
//It gets or sets thewindow title.
return function(title){
return title ? (window.title= title) :
inventory.storeName + ' ' + cart.name;
}
}
);
define(function(require){
var mod = require("./relative/name");
return mod;
});
JSONP可以跨域,应用非常广泛,使用JSONP模块时,callback必须设成’define’。
require(["http://example.com/api/data.json?callback=define"],
function (data) {
//The data object will be the APIresponse for the
//JSONP data call.
console.log(data);
}
);
这种方法一般用于初始化,但错误处理比较复杂。
CMD(Common ModuleDefinition)规范,跟AMD相比,CMD更像CommonJS。至于CMD和AMD哪个好哪个坏,完全没有定论的,主要看个人喜好。CMD的主接口也叫define,其参数可以是string、object、function。define的用法也可以像AMD一样,带id、依赖、回调,但这样就不属于CMD规范了。基本格式如下:
define(function (require, exports, module) {
// todo
});
第一个形参require是一个方法,用来加载外部模块,用法跟2.4类似:
define(function (require, exports) {
// 获取模块 a 的接口
var a = require('./a');
// 调用模块 a 的方法
a.doSomething();
});
但是这个require是同步模式加载,即如果模块没加载完毕,require后面的语句是不会执行的。AMD规范中都是异步加载,在回调function中使用require,其实相当于告诉构建器要在依赖队列中加入相关模块。
require同样有异步加载功能,异步加载后,执行响应的回调:
define(function(require, exports, module) {
// 异步加载一个模块,在加载完成时,执行回调
require.async('./b',function(b) {
b.doSomething();
});
// 异步加载多个模块,在加载完成时,执行回调
require.async(['./c','./d'], function(c, d) {
c.doSomething();
d.doSomething();
});
});
exports是一个对象,是模块的对外接口,即AMD规范中的return部分,当然,也可以像AMD规范一样,利用return返回接口。两种方式都可以,非常灵活。
define(function(require, exports) {
// 对外提供 foo 属性
exports.foo = 'bar';
// 对外提供 doSomething 方法
exports.doSomething =function() {};
});
define(function(require) {
// 通过 return 直接提供接口
return {
foo: 'bar',
doSomething: function() {}
};
});
值得注意的是,exports形参是不能重新定义的,因为重新定义后,原引用就断掉了,下面的做法起不到输出模块的作用:
define(function(require, exports) {
// 错误用法!!!
exports = {
foo: 'bar',
doSomething: function() {}
};
});
module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。
module.id为模块标识,module.uri为模块绝对路径,module.dependencies为模块的依赖数组,module.exports为模块的输出引用。也就是说,3.2节最后的例子中,如果这样写,就是可以工作的:
define(function(require, exports, module) {
// 正确写法
module.exports = {
foo: 'bar',
doSomething: function() {}
};
});
同时,对module.exports的赋值必须是同步的,放在settimeout里面是不行的。
可以看出,模块化编程为我们提供了良好的环境,在这个环境中,可以进行更明确的分工合作,让JS开发更专业化。同时,AMD规范和CMD规范都提供了异步加载,让Web前端应用更加高效,用户体验更好。
至于这两种规范哪个好一些,这个完全看个人编码习惯了,适合你的,就是好的。不过总体来看,CMD有相当一部分兼容了AMD,其最大的区别还是对依赖的引用上。
AMD主要通过回调函数的形参对依赖进行引用,有多少个依赖,就有多少个形参,当然如果依赖很多也可以不写那么多形参,通过arguments引用,或者在回调函数内部require也是一样的。这里需要说明的是,AMD规范中,尤其是RequireJS中,如果通过var a = require(‘module-name’);这种形式引入依赖,那么module-name必须要在依赖数组中存在,否则会报错。
CMD规范的依赖引用主要是在回调函数中直接require了,并且这种require是同步的,也提供了异步接口,这样做,势必要影响运行期性能。但是想AMD那种预加载依赖的方式,势必要影响加载时间。这两种影响哪个能容忍,哪个不能容忍,应该具体情况具体分析,视应用而定。
CMD规范定义:
https://github.com/seajs/seajs/issues/242
Javascript的AMD:
http://www.cnblogs.com/happyPawpaw/archive/2012/05/31/2528864.html
RequireJS中文网:
http://www.requirejs.cn/
Sea.js文档:
http://seajs.org/docs/#docs