随着浏覽器功能越來越完善,前端已經不僅僅是切圖做網站,前端在某些方面已經媲美桌面應用。越來越龐大的前端項目,越來越複雜的代碼,前端開發者們對于子產品化的需求空前強烈。後來node出現了,跟随node出現的還有commonjs,這是一種js子產品化解決方案,像node.js主要用于伺服器的程式設計,加載的子產品檔案一般都已經存在本地硬碟,是以加載起來比較快,不用考慮異步加載的方式,commonjs 加載子產品是同步的,是以隻有加載完成才能執行後面的操作。但是浏覽器環境不同于node,浏覽器中擷取一個資源必須要發送http請求,從伺服器端擷取,采用同步模式必然會阻塞浏覽器程序出現假死現象。在這方面dojo曾經做了偉大嘗試,早期dojo便是采用xhr+eval的方式,結果可想而知,阻塞現象是必然的。後來出現無阻塞加載腳本方式在開發中廣泛應用,在此基礎結合commonjs規範,前端子產品化迎來了兩種方案:amd、cmd.

關于二者的差別,前人之述備矣:
<a href="http://my.oschina.net/felumanman/blog/263330?p=1">關于 commonjs amd cmd umd</a>
<a href="http://www.infoq.com/cn/articles/browser-resource-loading-optimization">讓我們再聊聊浏覽器資源加載優化</a>
<a href="http://www.douban.com/note/283566440/">seajs與requirejs最大的差別</a>
<a href="http://www.zhihu.com/question/21347409#answer-2323656">yui modules 與 amd/cmd,哪一種方式更好?</a>
<a href="http://blog.chinaunix.net/uid-26672038-id-4112229.html">javasript子產品規範 - amd規範與cmd規範介紹</a>
而在本文,我們僅從代碼愛好者的角度來一窺二者api、子產品管理、加載、執行的異同。
對比amd與cmd規範,二者最大的差別在于依賴子產品的執行時期,cmd規範中明确要求延遲執行(execution must be lazy.)。這一點從二者在子產品的定義方法define的函數簽名上可以看出:
amd中define如下定義:
id:string類型,它指定了子產品被定義時的id;可選的,如果省略,子產品id預設使用加載器請求的響應腳本的子產品id。
dependencies是一個子產品定義時要求的依賴項的子產品id數組字面量。這些依賴項必須在factory方法執行前被解析,解析值應當被當做參數傳遞給factory函數;factory的參數位置符合子產品在依賴項中的索引。
factory,是一個被用來執行子產品初始化的參數或者是一個對象。如果factory是一個函數,它應當隻能被用來執行一次。如果factory參數是一個對象,這個對象呗用來作為子產品的輸出值。如果factory函數傳回一個值(對象、函數、任何可以被強制轉換為true的值),這個值将會被作為子產品的輸出值。
cmd中子產品如下定義:
一個子產品使用define函數來定義
define函數隻接受一個子產品工廠作為參數
factory必須是一個函數或者其他有效值
如果factory是一個函數,如果指定參數的話,前三個必須是“require”,“exports”,“module”
如果factory不是一個函數,那麼子產品的exports屬性被設定為那個有效對象
需要提一下的是二者對待依賴子產品的加載是一緻的,在factory執行時,依賴子產品都已被加載。從代碼上來看,amd中在begin處a、b的factory都是執行過的;而cmd中雖然a、b子產品在begin已被加載,但尚未執行,需要調用require執行依賴子產品。這就是cmd中着重強調的延遲執行。如果這個例子不明顯的話,我們來看一下條件依賴:
amd:
cmd:
條件依賴意思是我們根據條件使用依賴項,在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标簽)加載子產品
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、社群的活躍度