以代碼愛好者角度來看AMD與CMD(轉)

隨着瀏覽器功能愈來愈完善,前端已經不只僅是切圖作網站,前端在某些方面已經媲美桌面應用。愈來愈龐大的前端項目,愈來愈複雜的代碼,前端開發者們對於模塊化的需求空前強烈。後來node出現了,跟隨node出現的還有commonjs,這是一種js模塊化解決方案,像Node.js主要用於服務器的編程,加載的模塊文件通常都已經存在本地硬盤,因此加載起來比較快,不用考慮異步加載的方式,CommonJS 加載模塊是同步的,因此只有加載完成才能執行後面的操做。可是瀏覽器環境不一樣於Node,瀏覽器中獲取一個資源必需要發送http請求,從服務器端獲取,採用同步模式必然會阻塞瀏覽器進程出現假死現象。在這方面dojo曾經作了偉大嘗試,早期dojo即是採用xhr+eval的方式,結果可想而知,阻塞現象是必然的。後來出現無阻塞加載腳本方式在開發中普遍應用,在此基礎結合commonjs規範,前端模塊化迎來了兩種方案:AMD、CMD.javascript

  借用三藏法師一句話:人是人他媽生的,妖是妖他媽生的。此話雖不雅,但用這裏卻頗爲貼切。AMD 是 RequireJS 在推廣過程當中對模塊定義的規範化產出,CMD是SeaJS 在推廣過程當中被普遍認知。RequireJs出自dojo加載器的做者James Burke,SeaJs出自國內前端大師玉伯。兩者的區別,玉伯在12年如是說html

複製代碼
RequireJS 和 SeaJS 都是很不錯的模塊加載器,二者區別以下:

1. 二者定位有差別。RequireJS 想成爲瀏覽器端的模塊加載器,同時也想成爲 Rhino / Node 等環境的模塊加載器。SeaJS 則專一於 Web 瀏覽器端,同時經過 Node 擴展的方式能夠很方便跑在 Node 服務器端

2. 二者遵循的標準有差別。RequireJS 遵循的是 AMD(異步模塊定義)規範,SeaJS 遵循的是 CMD (通用模塊定義)規範。規範的不一樣,致使了二者 API 的不一樣。SeaJS 更簡潔優雅,更貼近 CommonJS Modules/1.1 和 Node Modules 規範。

3. 二者社區理念有差別。RequireJS 在嘗試讓第三方類庫修改自身來支持 RequireJS,目前只有少數社區採納。SeaJS 不強推,而採用自主封裝的方式來「海納百川」,目前已有較成熟的封裝策略。

4. 二者代碼質量有差別。RequireJS 是沒有明顯的 bug,SeaJS 是明顯沒有 bug。

5. 二者對調試等的支持有差別。SeaJS 經過插件,能夠實現 Fiddler 中自動映射的功能,還能夠實現自動 combo 等功能,很是方便便捷。RequireJS 無這方面的支持。

6. 二者的插件機制有差別。RequireJS 採起的是在源碼中預留接口的形式,源碼中留有爲插件而寫的代碼。SeaJS 採起的插件機制則與 Node 的方式一致:開放自身,讓插件開發者可直接訪問或修改,從而很是靈活,能夠實現各類類型的插件。.
複製代碼

  關於兩者的區別,前人之述備矣:前端

  而在本文,咱們僅從代碼愛好者的角度來一窺兩者API、模塊管理、加載、執行的異同。瀏覽器

  對比AMD與CMD規範,兩者最大的區別在於依賴模塊的執行時期,CMD規範中明確要求延遲執行(Execution must be lazy.)。這一點從兩者在模塊的定義方法define的函數簽名上能夠看出:服務器

  AMD中define以下定義:
   define(id?, dependencies?, factory);
  •  id:String類型,它指定了模塊被定義時的id;可選的,若是省略,模塊id默認使用加載器請求的響應腳本的模塊id。
  • dependencies是一個模塊定義時要求的依賴項的模塊id數組字面量。這些依賴項必須在factory方法執行前被解析,解析值應當被當作參數傳遞給factory函數;factory的參數位置符合模塊在依賴項中的索引。
  • factory,是一個被用來執行模塊初始化的參數或者是一個對象。若是factory是一個函數,它應當只能被用來執行一次。若是factory參數是一個對象,這個對象唄用來做爲模塊的輸出值。若是factory函數返回一個值(對象、函數、任何能夠被強制轉換爲true的值),這個值將會被做爲模塊的輸出值。

 

define(["./a", "./b"], function(a, b) {
  //BEGIN
  a.doSomething();
  b.doSomething();
});

 

  CMD中模塊以下定義:

define(function(require, exports, module) {

  // The module code goes here

});
  一個模塊使用define函數來定義
  1. define函數只接受一個模塊工廠做爲參數
  2. factory必須是一個函數或者其餘有效值
  3. 若是factory是一個函數,若是指定參數的話,前三個必須是「require」,「exports」,「module」
  4. 若是factory不是一個函數,那麼模塊的exports屬性被設置爲那個有效對象
define(function(require, exports, module) {
  //BEGIN
  require("./a").doSomething();
  require("./b").doSomething();
});

  須要提一下的是兩者對待依賴模塊的加載是一致的,在factory執行時,依賴模塊都已被加載。從代碼上來看,AMD中在BEGIN處a、b的factory都是執行過的;而CMD中雖然a、b模塊在BEGIN已被加載,但還沒有執行,須要調用require執行依賴模塊。這就是CMD中着重強調的延遲執行。若是這個例子不明顯的話,咱們來看一下條件依賴:

  AMD:

複製代碼
define(["./a", "./b"], function(a, b) {
  //BEGIN
  if (true) {
    a.doSomething();
  } else {
b.doSomething();
  }
  //END
});
複製代碼

  CMD:

複製代碼
define(function(require) {
   // BEGIN
  if(some_condition) {
    require('./a').doSomething();
  } else {
    require('./b').soSomething();
  }
  // END
});
複製代碼

  條件依賴意思是咱們根據條件使用依賴項,在AMD中BEGIN位置處a、b模塊都須要被執行一次。CMD中BEGIN處a、b都沒有被執行,在END處,a、b只有一個被實際執行過。

 

  那麼問題來了,javascript做爲腳本語言,代碼確定是順序執行的,做爲AMD與CMD的實現者,requireJs與seaJs是如何知道須要加載的全部文件呢?又是如何作到異步加載?對於seajs,factory中代碼確定是順序執行的,可是這必須致使require時的阻塞加載,而她又是如何保證異步加載的?

  每個卓越的思想都有一份樸實的代碼實現。因此不管AMD與CMD都要面臨如下幾個問題:

  一、模塊式如何註冊的,define函數都作了什麼?
  二、他們是如何知道模塊的依賴?
  三、如何作到異步加載?尤爲是seajs如何作到異步加載延遲執行的?
  辯證法第一規律:事物之間具備有機聯繫。AMD與CMD都借鑑了CommonJs,宏觀層面必有一致性,好比總體處理流程:
 
 
  模塊的加載解析到執行過程一共經歷了6個步驟:
  一、由入口進入程序
  二、進入程序後首先要作的就是創建一個模塊倉庫(這是防止重複加載模塊的關鍵),JavaScript原生的object對象最爲適合,key表明模塊Id,value表明各個模塊,處理主模塊
  三、向模塊倉庫註冊一模塊,一個模塊最少包含四個屬性:id(惟一標識符)、deps(依賴項的id數組)、factory(模塊自身代碼)、status(模塊的狀態:未加載、已加載未執行、已執行等),放到代碼中固然仍是object最合適
  四、模塊便是JavaScript文件,使用無阻塞方式(動態建立script標籤)加載模塊
scriptElement= document.createElement('script');
scriptElement.src = moduleUrl;
scriptElement.async = true;
scriptElement.onload = function(){.........};
document.head.appendChild(scriptElement);

  五、模塊加載完畢後,獲取依賴項(amd、cmd區別),改變模塊status,由statuschange後,檢測全部模塊的依賴項。

  因爲requirejs與seajs遵循規範不一樣,requirejs在define函數中能夠很容易得到當前模塊依賴項。而seajs中不須要依賴聲明,因此必須作一些特殊處理纔可否得到依賴項。方法將factory做toString處理,而後用正則匹配出其中的依賴項,好比出現require(./a),則檢測到須要依賴a模塊。

  同時知足非阻塞和順序執行就須要須要對代碼進行一些預處理,這是因爲CMD規範和瀏覽器環境特色所決定的。

  六、若是模塊的依賴項徹底加載完畢(amd中須要執行完畢,cmd中只須要文件加載完畢,注意這時候的factory還沒有執行,當使用require請求該模塊時,factory纔會執行,因此在性能上seajs遜於requirejs),執行主模塊的factory函數;不然進入步驟3.

 

  最後,不管requireJs仍是seaJs都已被普遍應用於web開發中,實際選取時應根據如下幾方面綜合平衡選取:

  一、功能可否知足項目需求
  二、文檔、demo的詳盡程度
  三、框架的學習曲線
  四、社區的活躍度
相關文章
相關標籤/搜索