在早期的前端开发中,JS所做的事情很少,只是辅助服务器进行验证等,并不涉及到太多太复杂的逻辑,所以把所有逻辑写到一个单文件是很直观的想法。但随着前端开发的不断发展,前端已经告别了当初刀耕火种的年代,单文件的前端开发逐渐凸显出其弊端。类似于C语言的include等把各模块组装起来的方法在JS开发中也渐渐流行起来。

AMD vs. CMD

Q: AMD与CMD分别是指什么?

A: AMD全称是Asynchronous Module Definition,是RequireJS在推广过程中对模块规范化的产出,模块与其依赖模块能够异步地被加载,这对浏览器端的开发是非常有用的。而CMD全称为Common Module Definition,它的宗旨是尽可能地推迟模块的执行,这与AMD是相反的(当然,RequireJS现在也开始改成延迟执行)。二者另一个很大的区别在于AMD推崇依赖前置,而CMD则推崇依赖后置。

Q:AMD与CMD怎么使用?

以符合AMD规范的require.js为例,在script标签加载require.js文件的时候,我们需要加一个data-main的入口文件来加载我们的模块,然后在这个入口文件中配置require的一些文件及目录信息,使用require加载模块并传入回调函数,当这些模块异步加载完成后就会调用回调函数使用这些模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script src="./lib/require.js" data-main="./config"></script>
// config.js
require.config({
baseUrl: './lib'
path: {
jQuery: 'jQuery/dist/jQuery.min',
underscore: 'underscore/dist/underscore.min'
}
});
// 以baseUrl为基目录
require(['../src/core', 'jQuery', 'underscore'], function (core, $, _) {
// do something
});
// src/core.js --- 不需要额外的模块的时候,可以直接使用define加回调函数
define(function (coremodule) {
// do something
});

而对于CMD规范,使用就比较简单了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script src="./lib/sea.js"></script>
<script>
seajs.config({
base: './lib',
alias: {
jQuery: 'jQuery/dist/jQuery.min',
}
});
// 入口模块
seajs.use('../src/main');
</script>
// 在模块内部使用define定义,require引入依赖
define(function (require, exports, module) {
var $ = require('jquery'),
_ = require('underscore');
});

Q:都有哪些符合AMD与CMD规范的实现?

  • AMD:require.js
  • CMD:sea.js

http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html

需要了解require库的使用(包括optimizer文件合并工具)

参考:

JS的模块化与require.js

JavaScript在设计之初,只被考虑用于简单的页面验证,因而当时的JS代码完全可以在一个文件中搞定。但是,随着 Web App化,大型的JS开发已经势在必行,这时再把所有的代码都写到一个文件中就极不利于开发。因此,在JS社区,大家开始考虑如何把JS代码模块化。

最直观的一个想法是使用立即函数

1
2
3
4
5
6
7
8
var module = (function () {
var variable,
fn = function () {},
export = {};
export.A = ...;
export.B = function () {};
return export;
}());

其好处在于可以封装私有成员与私有函数。但是,如果每个文件写成一个立即函数,那就意味着最后会生成一系列的模块,当然,完全可以将导出的模块赋给一个全局变量,即:

1
2
var global.moduleA = (function () {
}());

前面也提到,这是一个最直观的想法。事实上,它并没有解决文件的依赖关系,即某一个模块依赖于其它模块。为了解决它,只能通过小心地在加载顺序上保证依赖关系。

参考其它语言,我们当然也可以构造出一个类似于#include "moduleA.js"这种语法的加载器。考虑到JS中一切都是对象,我们可以使用var moduleA = require('moduleA.js')这种形式保证模块A一定先加载,然后再执行后续的操作。事实上,这也是 CommonJS 规范的核心内容, Node.js 就是采用这种方式来加载多个模块。

但是,这种方法有一个严重的弊端:它后续的操作一定是要等到依赖模块先加载完,即加载模块的过程会阻塞后续的所有操作。在Node.js中,这当然不是问题,因为所有文件都是本地的。但是在浏览器端,从服务器上加载文件是会有很长的延时,况且它还取决于网络环境。如果文件没有加载完,整个网页都被阻塞,势必会造成体验上的不适。因此,AMD(Asynchronous Module Definition)规范应运而生。它的大致结构是:

1
2
3
define(['moduleA', 'moduleB'], function (mA, mB) {
return ...;
});

将实际上模块内容放在回调函数中,当依赖模块加载完成后,就会自动构造出新的模块。

将整个项目文件模块化会极大地提高开发的效率,同时整个代码可读性也更强。但是,如果直接将这些文件部署到实际生产环境,会有很大的问题。要知道,在浏览器环境中,每个JS文件对应于一个HTTP请求,会导致整个网页性能的极速下降。因此,在实际用于生产环境前,我们需要把所有代码合并到一个文件中(有时候,我们还会进行代码的压缩以减小JS文件的大小),require.js有一个对应的optimizer就是做这件事情。

REF:[ http://www.zhihu.com/question/20351507 ]

兼容多种规范

当我们的模块发布出去的时候,通常我们希望它能够在多平台下都能正常使用,这就需要我们的代码兼容上述的多种平台了。一般情况下,我们需要把整个类库代码封装到一个闭包中,通过在闭包内部实现不同环境的处理。处理方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+function (name, factory) {
if (typeof define === 'function') {
define(factory); // AMD or CMD
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = factory(); // Node
} else {
this[name] = factory(); // browser
}
}('demo-module', function () {
var hello = function () {
console.log('hello');
}
return hello;
});