歡迎光臨
每天分享高質量文章

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

作者:木的樹

網址:http://www.cnblogs.com/dojo-lzz/p/4707725.html

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

借用三藏法師一句話:人是人他媽生的,妖是妖他媽生的。此話雖不雅,但用這裡卻頗為貼切。AMD 是 RequireJS 在推廣過程中對模組定義的規範化產出,CMD是SeaJS 在推廣過程中對模組定義的規範化產出。一位出自dojo載入器的作者James Burke,一位出自國內前端大師玉伯。二者的區別,玉伯在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 的方式一致:開放自身,讓外掛開發者可直接訪問或修改,從而非常靈活,可以實現各種型別的外掛。

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

  • 關於 CommonJS AMD CMD UMD
  • 讓我們再聊聊瀏覽器資源載入最佳化
  • SeaJS與RequireJS最大的區別
  • YUI Modules 與 AMD/CMD,哪一種方式更好?
  • JavaSript模組規範 – AMD規範與CMD規範介紹

而在本文,我們僅從程式碼愛好者的角度來一窺二者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、社群的活躍度

贊(0)

分享創造快樂