踏上前端这条道路以来,我一直以为自己就是个娴熟的切图工,每天只需要做着重复的劳动,切图,做网站。然而,技术的发展是日新月异的,切图工早就面临淘汰。随着浏览器功能越来越完善,前端项目越来越大,代码越来越复杂,为了适应这种变化,越来越要求前端开发模块化。
首先介绍一下什么是模块化。模块化是指解决某一个复杂问题,按照一种分类的思维把问题进行系统分解,并处理。譬如将巨大的系统代码,整合优化分割成高内聚低耦合的模块,达到便于管理,维护和开发的目的。
模块化设计,必须实现以下能力:
- 定义封装的模块
- 定义新模块对其他模块的以来
- 对其他模块的引入支持。
介绍其中最主要的两种规范,CMD和AMD。
AMD 与 RequireJS
AMD:Asynchronous Module Definition 异步模块定义 [e'sɪŋkrənəs]
AMD采用require([module],callback);加载模块,require有两个参数,一个是依赖的模块数组,一个是回调函数。目前主要有两个Javascript库实现了AMD规范,require.js和curl.js
require.js主要解决以下两个问题:
(1)实现js文件的异步加载,避免网页失去响应;
(2)管理模块之间的依赖性,便于代码的编写和维护。
require.js使用方法
(1)下载js加载入页面
(2)加载自己的代码 <script src="js/require.js" data-main="js/main"></script>
main.js 1 require(['moduleA','moduleB'],function(moduleA,moduleB){
//code here
});
//假定主模块依赖jquery、underscore和backbone这三个模块,main.js就可以这样写:
require(['jquery', 'underscore', 'backbone'], function ($, _, Backbone){ // some code here });
模块的加载
require.config(); config方法可以对模块的加载行为进行自定义。require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径,baseUrl为基目录
//main.js
require.config({ baseUrl:"js/lib", paths:{ "jquery":"jquery.min", "underscore":"underscore".min, "backbone":"backbone.min" } })
AMD模块的写法
模块必须采用特定的define()函数来定义,如果一个模块不依赖于其他模块可以直接定义在define()函数中。
假定一个math模块
// math.js define(function(){ var add = function(x,y){ return x+y; }; return {add:add}; }); // 加载方法 main.js require(['math'],function(math){ alert(math.add(1,1)); });
如果这个模块依赖其他模块,define()函数的第一个参数必须是一个数组,指明该模块的依赖性
define(['myLib'],function(myLib){ function foo(){ myLib.func(); } return {foo:foo}; }); //当require()函数加载上面这个模块的时候,就会先加载myLib.js文件。
require方法调用模块
require(['module1','module2'],function(module1,module2){ module1.func(); module2.func(); }) //在加载 foo 与 bar 两个模块之后执行回调函数实现具体过程。
同时还可以在define定义模块内部进行require调用模块
define( function( require ){
var m1 = require( 'module1' ),
m2 = require( 'module2' );
...
});
加载非规范的模块
在require()加载之前,先用require.config()方法,定义他们的一些特征。
require.config({ shim:{ 'underscore':{ exports:'_' }, 'backbone':{ deps:['underscore','jquery'], exports:'Backbone'
} } }); /*require.config()接受一个配置对象,这个对象除了有前面说过的paths属性之外,还有一个shim属性,专门用来配置不兼容的模块。具体来说,每个模块要定义
(1)exports值(输出的变量名),表明这个模块外部调用时的名称;
(2)deps数组,表明该模块的依赖性。*/ //比如jQuery插件定义 shim:{ 'jquery.scroll':{ deps:['jquery'], exports:'jQuery.fn.scroll'
} }
CMD 与 seaJS (转载)
CMD:Common Module Definition 通用模块加载规范
在CMS中一个模块就是一个文件,格式和AMD有些类似,define(factory);define定义模块,参数factory可以是一个函数或者对象,字符串,当factory为对象字符串时,表示模块的接口是该对象。
定义JSON数据模块:
define({"foo":"bar"});
通用字符串定义模板模块
define('this is {{data}}!');
factory 为函数的时候,表示模块的构造方法,执行构造方法便可以得到模块向外提供的接口。
define( function(require, exports, module) {
// 模块代码
});
define( id?, deps?, factory );
define也可以接受两个以上的参数,字符串id为模块标识,数组deps为模块依赖:
define( 'module', ['module1', 'module2'], function( require, exports, module ){
// 模块代码
});
其与 AMD 规范用法不同。
define(function( require, exports ){
var a = require('./a');
a.doSomething();
});
require.async( id, callback? );
require是同步往下执行的,需要的异步加载模块可以使用 require.async 来进行加载:
define( function(require, exports, module) {
require.async('.a', function(a){
a.doSomething();
});
});
exports 是 factory 的第二个参数,用来向外提供模块接口。
define(function( require, exports ){
exports.foo = 'bar'; // 向外提供的属性
exports.do = function(){}; // 向外提供的方法
});
当然也可以使用 return 直接向外提供接口。
define(function( require, exports ){
return{
foo : 'bar', // 向外提供的属性
do : function(){} // 向外提供的方法
}
});
也可以简化为直接对象字面量的形式:
define({
foo : 'bar', // 向外提供的属性
do : function(){} // 向外提供的方法
});
与nodeJS中一样需要注意的是,一下方式是错误的:
define(function( require, exports ){
exports = {
foo : 'bar', // 向外提供的属性
do : function(){} // 向外提供的方法
}
});
需要这么做
define(function( require, exports, module ){
module.exports = {
foo : 'bar', // 向外提供的属性
do : function(){} // 向外提供的方法
}
});
传 入的对象引用可以添加属性,一旦赋值一个新的对象,那么值钱传递进来的对象引用就会失效了。开始之初,exports 是作为 module.exports 的一个引用存在,一切行为只有在这个引用上 factory 才得以正常运行,赋值新的对象后就会断开引用,exports就只是一个新的对象引用,对于factory来说毫无意义,就会出错。
seaJS
seajs.use 用来在页面中加载一个或者多个模块
// 加载一个模块
seajs.use('./a');
// 加载模块,加载完成时执行回调
seajs.use('./a',function(a){
a.doSomething();
});
// 加载多个模块执行回调
seajs.use(['./a','./b'],function(a , b){
a.doSomething();
b.doSomething();
});
AMD 与 CMD 的区别
下面是玉伯(sea.js创始人)对于 AMD 与 CMD 区别的解释:
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
}) // AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
// ...
})
API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部
require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use
来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。
另外,SeaJS 和 RequireJS 的差异,可以参考:https://github.com/seajs/seajs/issues/277
amdjs 的 require 接口文档 https://github.com/amdjs/amdjs-api/wiki/require
amdjs 的接口文档 https://github.com/amdjs/amdjs-api/wiki
RequireJS和AMD规范 http://javascript.ruanyifeng.com/tool/requirejs.html
知乎 AMD 和 CMD 的区别有哪些? http://www.zhihu.com/question/20351507
JavaScript模块化开发 - AMD规范 http://www.feeldesignstudio.com/2013/09/javascript-module-pattern-amd
模块化设计 http://baike.baidu.com/view/189730.htm
模块化 http://baike.baidu.com/view/182267.htm