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


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

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

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都要面臨以下幾個問題:

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

  5、模塊加載完畢后,獲取依賴項(amd、cmd區別),改變模塊status,由statuschange后,檢測所有模塊的依賴項。

  由於requirejs與seajs遵循規范不同,requirejs在define函數中可以很容易獲得當前模塊依賴項。而seajs中不需要依賴聲明,所以必須做一些特殊處理才能否獲得依賴項。方法將factory作toString處理,然后用正則匹配出其中的依賴項,比如出現require(./a),則檢測到需要依賴a模塊。

  同時滿足非阻塞和順序執行就需要需要對代碼進行一些預處理,這是由於CMD規范和瀏覽器環境特點所決定的。

  6、如果模塊的依賴項完全加載完畢(amd中需要執行完畢,cmd中只需要文件加載完畢,注意這時候的factory尚未執行,當使用require請求該模塊時,factory才會執行,所以在性能上seajs遜於requirejs),執行主模塊的factory函數;否則進入步驟3.

 

  最后,無論requireJs還是seaJs都已被廣泛應用於web開發中,實際選取時應根據以下幾方面綜合平衡選取:

  1、功能能否滿足項目需求
  2、文檔、demo的詳盡程度
  3、框架的學習曲線
  4、社區的活躍度

 

  如果您覺得這篇文章對您有幫助,請不吝點擊推薦。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM