AMD和CMD的区别和联系

模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问题进行系统性的分解以之处理。模块化是一种处理复杂系统分解为代码结构更合理,可维护性更高的可管理的模块的方式。可以想象一个巨大的系统代码,被整合优化分割成逻辑性很强的模块时,对于软件是一种何等意义的存在。对于软件行业来说:解耦软件系统的复杂性,使得不管多么大的系统,也可以将管理,开发,维护变得“有理可循”。

javascript并不具有传统意义上的类,继承,模块。但Javascript社区做了很多努力,在现有的运行环境中,实现”模块”的效果。


AMD|CMD出现的原因

node出现时,跟随node出现的还有commonjs,这是一种js模块化解决方案,像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,其等待时间就是硬盘的读取时间。不用考虑异步加载的方式,CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。

但是浏览器环境不同于Node,浏览器中获取一个资源必须要发送http请求,从服务器端获取,采用同步模式必然会阻塞浏览器进程出现假死现象

比如下面这个例子中,由于浏览器加载js默认是阻塞的,那么后面的add过程就必须等前面require完成,这个时候就会造成假死现象:

1
2
var math = require(math);
math.add(5,6);

因此,浏览器端的模块,不能采用”同步加载”(synchronous),只能采用”异步加载”(asynchronous)。后来出现无阻塞加载脚本方式在开发中广泛应用,在此基础结合commonjs规范,前端模块化迎来了两种方案:AMD、CMD。这就是两种规范诞生的背景。


AMD|CMD的定义

AMD(Asynchronous Module Definition),CMD(Common Module Definition)

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出,CMD是SeaJS 在推广过程中被广泛认知。RequireJs出自dojo加载器的作者James Burke,SeaJs出自国内前端大师玉伯。


AMD

Asynchronous Module Definition,用白话文讲就是 异步模块定义,,所有的模块将被异步加载,模块加载不影响后面语句运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

它包含三个参数

1
define(id?, dependencies?, factory);

第一个参数, id 为字符串类型,表示了模块标识,为可选参数。若不存在则模块标识应该默认定义为在加载器中被请求脚本的标识。如果存在,那么模块标识必须为顶层的或者一个绝对的标识。

第二个参数,dependencies ,是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。

第三个参数,factory,是一个需要进行实例化的函数或者一个对象。


下面例子来自官方文档

创建模块标识为 alpha 的模块,依赖于 require, export,和标识为 beta 的模块

1
2
3
4
5
6
7
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});

一个返回对象字面量的异步模块

1
2
3
4
5
6
7
define(["alpha"], function( alpha ){
return {
verb : function(){
return alpha.verb() + 1 ;
}
}
});

无依赖模块可以直接使用对象字面量来定义

1
2
3
4
5
define( {
add : function( x, y ){
return x + y ;
}
} );

类似与 CommonJS 方式定义

1
2
3
4
5
define( function( require, exports, module){
var a = require('a'),
b = require('b');
exports.action = function(){};
} );


CMD

先来看看commonJS

CommonJS定义的模块分为:{模块引用(require)} {模块定义(exports)} {模块标识(module)}
require用来引入外部模块;exports对象用于导出当前模块的方法或变量,唯一的导出口;module对象就代表模块本身。

大名远扬的玉伯写了seajs,提出的CMD规范,格式为:

1
define(factory);

define 接受 factory 参数,factory 可以是一个函数,也可以是一个对象或字符串。

factory 为对象、字符串时,表示模块的接口就是该对象、字符串。比如可以如下定义一个 JSON 数据模块:

1
define({ "foo": "bar" });

factory 为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。

factory 方法在执行时,默认会传入三个参数:require、exports 和 module(是不是跟上面提到的commonJS很像呢):

1
2
3
define(function(require, exports, module) {
// 模块代码
});

define(function (require, exports, module)和exports(是一个对象,用来向外提供模块接口)详解

1
2
3
4
5
6
define(function(require, exports) {
// 对外提供 foo 属性
exports.foo = 'bar';
// 对外提供 doSomething 方法
exports.doSomething = function() {};
});

除了给 exports 对象增加成员,还可以使用 return 直接向外提供接口。

1
2
3
4
5
6
7
define(function(require) {
// 通过 return 直接提供接口
return {
foo: 'bar',
doSomething: function() {}
};
});

特别注意:下面这种写法是错误的!

1
2
3
4
5
6
7
define(function(require, exports) {
// 错误用法!!!
exports = {
foo: 'bar',
doSomething: function() {}
};
});

正确的写法是用 return 或者给 module.exports 赋值:

1
2
3
4
5
6
7
define(function(require, exports, module) {
// 正确写法
module.exports = {
foo: 'bar',
doSomething: function() {}
};
});

exports 仅仅是 module.exports 的一个引用。在 factory 内部给 exports 重新赋值时,并不会改变module.exports 的值。因此给 exports 赋值是无效的,不能用来更改模块接口。


AMD|CMD的区别

知乎上玉伯对于 AMD 与 CMD 区别的解释

  1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
  2. CMD 推崇依赖就近,AMD 推崇依赖前置。看代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 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()
    // ...
    })
  3. AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。

  4. AMD擅长在浏览器端、CMD擅长在服务器端。这是因为浏览器加载一个功能不像服务器那么快,有大量的网络消耗。所以一个异步loader是更接地气的。
  5. 总结:AMD | 速度快 | 会浪费资源 | 预先加载所有的依赖,直到使用的时候才执行
  6. 总结:CMD | 只有真正需要才加载依赖 | 性能较差 | 直到使用的时候才定义依赖