AMD, CMD, CommonJS和UMD

我是豆腐不是渣發表於2016-04-05

我的github(PS:希望star): https://github.com/tonyzheng1...

今天由於專案中引入的echarts的檔案太大,requirejs經常載入超時,不得不分開來載入echarts的各個圖表。但是使用echarts自帶的線上構建工具生成的支援AMD 標準的模組報錯,所以不得不使用echarts的全域性函式,使用requirejs的shim進行載入。藉此機會學習一下AMD, CMD, CommonJS和UMD各自的規範,和它們之間的區別。

Javascript模組化

在瞭解這些規範之前,還是先了解一下什麼是模組化。

模組化是指在解決某一個複雜問題或者一系列的雜糅問題時,依照一種分類的思維把問題進行系統性的分解以之處理。模組化是一種處理複雜系統分解為程式碼結構更合理,可維護性更高的可管理的模組的方式。可以想象一個巨大的系統程式碼,被整合優化分割成邏輯性很強的模組時,對於軟體是一種何等意義的存在。對於軟體行業來說:解耦軟體系統的複雜性,使得不管多麼大的系統,也可以將管理,開發,維護變得“有理可循”。

還有一些對於模組化一些專業的定義為:模組化是軟體系統的屬性,這個系統被分解為一組高內聚,低耦合的模組。那麼在理想狀態下我們只需要完成自己部分的核心業務邏輯程式碼,其他方面的依賴可以通過直接載入被人已經寫好模組進行使用即可。

首先,既然是模組化設計,那麼作為一個模組化系統所必須的能力:

  1. 定義封裝的模組。

  2. 定義新模組對其他模組的依賴。

  3. 可對其他模組的引入支援。

好了,思想有了,那麼總要有點什麼來建立一個模組化的規範制度吧,不然各式各樣的模組載入方式只會將局攪得更為混亂。那麼在JavaScript中出現了一些非傳統模組開發方式的規範 CommonJS的模組規範,AMD(Asynchronous Module Definition),CMD(Common Module Definition)等。

CommonJS

CommonJS是伺服器端模組的規範,Node.js採用了這個規範。

根據CommonJS規範,一個單獨的檔案就是一個模組。載入模組使用require方法,該方法讀取一個檔案並執行,最後返回檔案內部的exports物件。

例如:

// foobar.js
 
//私有變數
var test = 123;
 
//公有方法
function foobar () {
 
    this.foo = function () {
        // do someing ...
    }
    this.bar = function () {
        //do someing ...
    }
}
 
//exports物件上的方法和變數是公有的
var foobar = new foobar();
exports.foobar = foobar;
//require方法預設讀取js檔案,所以可以省略js字尾
var test = require('./boobar').foobar;
 
test.bar();

CommonJS 載入模組是同步的,所以只有載入完成才能執行後面的操作。像Node.js主要用於伺服器的程式設計,載入的模組檔案一般都已經存在本地硬碟,所以載入起來比較快,不用考慮非同步載入的方式,所以CommonJS規範比較適用。但如果是瀏覽器環境,要從伺服器載入模組,這是就必須採用非同步模式。所以就有了 AMD CMD 解決方案。

AMD和RequireJS

AMD

AMD是"Asynchronous Module Definition"的縮寫,意思就是"非同步模組定義".

AMD設計出一個簡潔的寫模組API:
define(id?, dependencies?, factory);
第一個引數 id 為字串型別,表示了模組標識,為可選引數。若不存在則模組標識應該預設定義為在載入器中被請求指令碼的標識。如果存在,那麼模組標識必須為頂層的或者一個絕對的標識。
第二個引數,dependencies ,是一個當前模組依賴的,已被模組定義的模組標識的陣列字面量。
第三個引數,factory,是一個需要進行例項化的函式或者一個物件。

通過引數的排列組合,這個簡單的API可以從容應對各種各樣的應用場景,如下所述。

  • 定義無依賴的模組

define( {
    add : function( x, y ){
        return x + y ;
    }
} );
  • 定義有依賴的模組

define(["alpha"], function( alpha ){
    return {
        verb : function(){
            return alpha.verb() + 1 ;
        }
    }
});
  • 定義資料物件模組

define({
    users: [],
    members: []
});
  • 具名模組

define("alpha", [ "require", "exports", "beta" ], function( require, exports, beta ){
    export.verb = function(){
        return beta.verb();
        // or:
        return require("beta").verb();
    }
});
  • 包裝模組

define(function(require, exports, module) {
    var a = require('a'),
          b = require('b');

    exports.action = function() {};
} );

不考慮多了一層函式外,格式和Node.js是一樣的:使用require獲取依賴模組,使用exports匯出API。

除了define外,AMD還保留一個關鍵字require。require 作為規範保留的全域性識別符號,可以實現為 module loader,也可以不實現。

模組載入

require([module], callback)

AMD模組化規範中使用全域性或區域性的require函式實現載入一個或多個模組,所有模組載入完成之後的回撥函式。

其中:

[module]:是一個陣列,裡面的成員就是要載入的模組;
callback:是模組載入完成之後的回撥函式。

例如:載入一個math模組,然後呼叫方法 math.add(2, 3);

require(['math'], function(math) {
 math.add(2, 3);
});

RequireJS

RequireJS 是一個前端的模組化管理的工具庫,遵循AMD規範,它的作者就是AMD規範的創始人 James Burke。所以說RequireJS是對AMD規範的闡述一點也不為過。

RequireJS 的基本思想為:通過一個函式來將所有所需要的或者說所依賴的模組實現裝載進來,然後返回一個新的函式(模組),我們所有的關於新模組的業務程式碼都在這個函式內部操作,其內部也可無限制的使用已經載入進來的以來的模組。

<script data-main='scripts/main' src='scripts/require.js'></script>

那麼scripts下的main.js則是指定的主程式碼指令碼檔案,所有的依賴模組程式碼檔案都將從該檔案開始非同步載入進入執行。

define用於定義模組,RequireJS要求每個模組均放在獨立的檔案之中。按照是否有依賴其他模組的情況分為獨立模組和非獨立模組。

  • 獨立模組,不依賴其他模組。直接定義:

define({
    method1: function(){},
    method2: function(){}
});

也等價於

define(function() {
    return {
        method1: function(){},
        method2: function(){}
    }
});
  • 非獨立模組,對其他模組有依賴。

define([ 'module1', 'module2' ], function(m1, m2) {
    ...
});

或者:

define(function(require) {
    var m1 = require('module1'),
          m2 = require('module2');
    ...
});

簡單看了一下RequireJS的實現方式,其 require 實現只不過是提取 require 之後的模組名,將其放入依賴關係之中。

  • require方法呼叫模組

在require進行呼叫模組時,其引數與define類似。

require(['foo', 'bar'], function(foo, bar) {
    foo.func();
    bar.func();
} );

在載入 foo 與 bar 兩個模組之後執行回撥函式實現具體過程。

當然還可以如之前的例子中的,在define定義模組內部進行require呼叫模組

define(function(require) {
    var m1 = require( 'module1' ),
          m2 = require( 'module2' );
    ...
});

define 和 require 這兩個定義模組,呼叫模組的方法合稱為AMD模式,定義模組清晰,不會汙染全域性變數,清楚的顯示依賴關係。AMD模式可以用於瀏覽器環境並且允許非同步載入模組,也可以按需動態載入模組。

官網 (http://www.requirejs.org/)
API (http://www.requirejs.org/docs...

CMD和SeaJS

CMD是SeaJS 在推廣過程中對模組定義的規範化產出

  • 對於依賴的模組AMD是提前執行,CMD是延遲執行。不過RequireJS從2.0開始,也改成可以延遲執行(根據寫法不同,處理方式不通過)。

  • CMD推崇依賴就近,AMD推崇依賴前置。

//AMD
define(['./a','./b'], function (a, b) {
 
    //依賴一開始就寫好
    a.test();
    b.test();
});
 
//CMD
define(function (requie, exports, module) {
     
    //依賴可以就近書寫
    var a = require('./a');
    a.test();
     
    ...
    //軟依賴
    if (status) {
     
        var b = requie('./b');
        b.test();
    }
});

雖然 AMD也支援CMD寫法,但依賴前置是官方文件的預設模組定義寫法。

  • AMD的API預設是一個當多個用,CMD嚴格的區分推崇職責單一。例如:AMD裡require分全域性的和區域性的。CMD裡面沒有全域性的 require,提供 seajs.use()來實現模組系統的載入啟動。CMD裡每個API都簡單純粹。

UMD

UMD是AMD和CommonJS的糅合

AMD模組以瀏覽器第一的原則發展,非同步載入模組。
CommonJS模組以伺服器第一原則發展,選擇同步載入,它的模組無需包裝(unwrapped modules)。
這迫使人們又想出另一個更通用的模式UMD (Universal Module Definition)。希望解決跨平臺的解決方案。

UMD先判斷是否支援Node.js的模組(exports)是否存在,存在則使用Node.js模組模式。
在判斷是否支援AMD(define是否存在),存在則使用AMD方式載入模組。

(function (window, factory) {
    if (typeof exports === 'object') {
     
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
     
        define(factory);
    } else {
     
        window.eventUtil = factory();
    }
})(this, function () {
    //module ...
});

相關文章