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 | 只有真正需要才加载依赖 | 性能较差 | 直到使用的时候才定义依赖