1. 程式人生 > >AMD、CMD和Common規範

AMD、CMD和Common規範

1.名詞解釋
AMD:Asynchronous Modules Definition非同步模組定義,提供定義模組及非同步載入該模組依賴的機制。
CMD:Common Module Definition 通用模組定義,提供模組定義及按需執行模組

RequireJS 遵循 AMD(非同步模組定義)規範,Sea.js 遵循 CMD (通用模組定義)規範。規範的不同,導致了兩者 API 不同。

2. 提前執行:提前非同步並行載入
優點:儘早執行依賴可以儘早發現錯誤;缺點:容易產生浪費
3. 延遲執行:延遲按需載入
優點:減少資源浪費 缺點:等待時間長、出錯時間延後

2.1 AMD與CMD程式碼模式

AMD程式碼模式-執行策略

define(['./a', './b'], function(a, b) { //執行至此,a.js和b.js已經下載完成 a模組和b模組已經執行完,直接可用;
    a.doing();
    // 此處省略500行程式碼
    b.doing();
});

CMD程式碼模式-執行策略

define(function(require, exports, module) {
     var a = require("./a"); //等待a.js下載、執行完
     a.doing();
     // 此處省略500行程式碼
     var b = require("./b"); //依賴就近書寫
b.doing(); });

3. AMD 的 API 預設是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一。比如 AMD 裡,require 分全域性 require 和區域性 require,都叫 require。CMD 裡,沒有全域性 require,而是根據模組系統的完備性,提供 seajs.use 來實現模組系統的載入啟動。CMD 裡,每個 API 都簡單純粹。

 

方案 | 優勢 | 劣勢 | 特點
AMD | 速度快 | 會浪費資源 | 預先載入所有的依賴,直到使用的時候才執行
CMD | 只有真正需要才載入依賴 | 效能較差 | 直到使用的時候才定義依賴

它們除了希望放在瀏覽器作為loader也能夠放在服務端,提供載入功能。在我看來,AMD擅長在瀏覽器端、CMD擅長在伺服器端。這是因為瀏覽器載入一個功能不像伺服器那麼快,有大量的網路消耗。所以一個非同步loader是更接地氣的。

或者,乾脆使用YUI3的模組機制,在上線前進行壓制。把互相依賴的模組壓在一個檔案中。

---------------------------------------------------------------------------------------------------

 

每一個卓越的思想都有一份樸實的程式碼實現。所以無論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.

 

  AMD規範定義了一個自由變數或者說是全域性變數 define 的函式

define( id?, dependencies?, factory );    

 

    第一個引數 id 為字串型別,表示了模組標識,為可選引數。若不存在則模組標識應該預設定義為在載入器中被請求指令碼的標識。如果存在,那麼模組標識必須為頂層的或者一個絕對的標識。     第二個引數,dependencies ,是一個當前模組依賴的,已被模組定義的模組標識的陣列字面量。     第三個引數,factory,是一個需要進行例項化的函式或者一個物件。 建立模組標識為 alpha 的模組,依賴於 require, export,和標識為 beta 的模組  
  1. define("alpha", [ "require", "exports", "beta" ], function( require, exports, beta ){
  2.     export.verb = function(){
  3.         return beta.verb();
  4.         // or:
  5.         return require("beta").verb();
  6.     }
  7. });
    一個返回物件字面量的非同步模組
  1. define(["alpha"], function( alpha ){
  2.     return {
  3.         verb : function(){
  4.             return alpha.verb() + 1 ;
  5.         }
  6.     }
  7. });
    無依賴模組可以直接使用物件字面量來定義
  1. define( {
  2.     add : function( x, y ){
  3.         return x + y ;
  4.     }
  5. } );
require(); 在 AMD 規範中的 require 函式與一般的 CommonJS中的 require 不同。由於動態檢測依賴關係使載入非同步,對於基於回撥的 require 需求強烈。

    區域性 與 全域性 的require

    區域性的 require 需要在AMD模式中的 define 工廠函式中傳入 require。
  1. define( ['require'], function( require ){
  2.   // ...
  3. } );
  4. or:
  5. define( function( require, exports, module ){
  6.   // ...
  7. } );
    區域性的 require 需要其他特定的 API 來實現。     全域性的 require 函式是唯一全域性作用域下的變數,像 define一樣。全域性的 require 並不是規範要求的,但是如果實現全域性的 require函式,那麼其需要具有與區域性 require 函式 一樣的以下的限定:     1. 模組標識視為絕對的,而不是相對的對應另一個模組標識。     2. 只有在非同步情況下,require的回撥方式才被用來作為互動操作使用。因為他不可能在同步的情況下通過 require(String) 從頂層載入模組。     依賴相關的API會開始模組載入。如果需要有互操作的多個載入器,那麼全域性的 reqiure 應該被載入頂層模組來代替。
  1. require(String)
  2. define( function( require ){
  3.     var a = require('a'); // 載入模組a
  4. } );
  5. require(Array, Function)
  6. define( function( require ){
  7.     require( ['a', 'b'], function( a,b ){ // 載入模組a b 使用
  8.         // 依賴 a b 模組的執行程式碼
  9.     } ); 
  10. } );
  11. require.toUrl( Url )
  12. define( function( require ){
  13.     var temp = require.toUrl('./temp/a.html'); // 載入頁面
  14. } );
define 和 require 這兩個定義模組,呼叫模組的方法合稱為AMD模式,定義模組清晰,不會汙染全域性變數,清楚的顯示依賴關係。AMD模式可以用於瀏覽器環境並且允許非同步載入模組,也可以按需動態載入模組。   在CMD中,一個模組就是一個檔案,格式為:     define( factory ); 全域性函式define,用來定義模組。     引數 factory  可以是一個函式,也可以為物件或者字串。     當 factory 為物件、字串時,表示模組的介面就是該物件、字串。     定義JSON資料模組:
  1. define({ "foo": "bar" });
    通過字串定義模板模組:
  1. define('this is {{data}}.');
    factory 為函式的時候,表示模組的構造方法,執行構造方法便可以得到模組向外提供的介面。
  1. define( function(require, exports, module) { 
  2.     // 模組程式碼
  3. });
define( id?, deps?, factory );     define也可以接受兩個以上的引數,字串id為模組標識,陣列deps為模組依賴:
  1. define( 'module', ['module1', 'module2'], function( require, exports, module ){
  2.     // 模組程式碼
  3. } );
    其與 AMD 規範用法不同。 require 是 factory 的第一個引數。     require( id );     接受模組標識作為唯一的引數,用來獲取其他模組提供的介面:
  1. define(function( require, exports ){
  2.     var a = require('./a');
  3.     a.doSomething();
  4. });
    require.async( id, callback? );     require是同步往下執行的,需要的非同步載入模組可以使用 require.async 來進行載入:
  1. define( function(require, exports, module) { 
  2.     require.async('.a', function(a){
  3.         a.doSomething();
  4.     });
  5. });
    require.resolve( id )     可以使用模組內部的路徑機制來返回模組路徑,不會載入模組。     exports 是 factory 的第二個引數,用來向外提供模組介面。
  1. define(function( require, exports ){
  2.     exports.foo = 'bar'; // 向外提供的屬性
  3.     exports.do = function(){}; // 向外提供的方法
  4. });
    當然也可以使用 return 直接向外提供介面。
  1. define(function( require, exports ){
  2.     return{
  3.         foo : 'bar', // 向外提供的屬性
  4.         do : function(){} // 向外提供的方法
  5.     }
  6. });
    也可以簡化為直接物件字面量的形式:
  1. define({
  2.     foo : 'bar', // 向外提供的屬性
  3.     do : function(){} // 向外提供的方法
  4. });
與nodeJS中一樣需要注意的是,一下方式是錯誤的:
  1. define(function( require, exports ){
  2.     exports = {
  3.         foo : 'bar', // 向外提供的屬性
  4.         do : function(){} // 向外提供的方法
  5.     }
  6. });
    需要這麼做
  1. define(function( require, exports, module ){
  2.     module.exports = {
  3.         foo : 'bar', // 向外提供的屬性
  4.         do : function(){} // 向外提供的方法
  5.     }
  6. });
   傳入的物件引用可以新增屬性,一旦賦值一個新的物件,那麼之前傳遞進來的物件引用就會失效了。開始之初,exports 是作為 module.exports 的一個引用存在,一切行為只有在這個引用上 factory 才得以正常執行,賦值新的物件後就會斷開引用,exports就只是一個新的物件引用,對於factory來說毫無意義,就會出錯。     module 是factory的第三個引數,為一個物件,上面儲存了一些與當前模組相關聯的屬性與方法。         module.id 為模組的唯一標識。         module.uri 根據模組系統的路徑解析規則得到模組的絕對路徑。         module.dependencies 表示模組的依賴。         module.exports 當前模組對外提供的介面。

 

 

CommonJS
這種方式通過一個叫做require的方法,同步載入依賴,然後返匯出API供其它模組使用,一個模組可以通過exports或者module.exports匯出API。CommonJS規範中,一個單獨的檔案就是一個模組。每一個模組都是一個單獨的作用域,在一個檔案中定義的變數,都是私有的,對其他檔案是不可見的。
Well
服務端模組可以很好的複用
這種風格的模組已經很多了,比如npm上基本上都是這種風格的module
簡單易用
Less Well
載入模組是同步的,所以只有載入完成才能執行後面的操作
多個模組不能並行載入
像Node.js主要用於伺服器的程式設計,載入的模組檔案一般都已經存在本地硬碟,所以載入起來比較快,不用考慮非同步載入的方式,所以CommonJS規範比較適用。但如果是瀏覽器環境,要從伺服器載入模組,這是就必須採用非同步模式。所以就有了 AMD 、CMD 的解決方案。

 

 

CommonJS規範

 CommonJS是在瀏覽器環境之外構建JavaScript生態系統為目標產生的專案,比如伺服器和桌面環境中。CommonJS規範是為了解決JavaScript的作用域問題而定義的模組形式, 可以使每個模組在它自身的名稱空間中執行。該規範的主要內容是:模組必須通過  module.exports匯出對外的變數或介面,通過require()來匯入其他模組的輸出到當前模組。 例子: [javascript]  view plain  copy        在CODE上檢視程式碼片派生到我的程式碼片
  1. // moduleA.js  
  2. module.exports = function( value ){  
  3.     return value * 2;  
  4. }  
[javascript]  view plain  copy        在CODE上檢視程式碼片派生到我的程式碼片
  1. // moduleB.js  
  2. var multiplyBy2 = require('./moduleA');  
  3. var result = multiplyBy2(4);  
CommonJS是同步載入模組,但其實也有瀏覽器端的實現,其原理是將所有模組都定義好並通過id進行索引,這樣就可以瀏覽器進行解析了
 伺服器端的Node.js遵循CommonJS規範。核心思想是允許模組通過require 方法來同步載入所要依賴的其他模組,然後通過 exports或module.exports來匯出需要暴露的介面。 [javascript]  view plain  copy        在CODE上檢視程式碼片派生到我的程式碼片
  1. require("module");  
  2. require("../file.js");  
  3. exports.doStuff = function() {};  
  4. module.exports = someValue;  
優點:
  •  伺服器端便於重用
  • NPM中已經將近20w個模組包
  • 簡單並容易使用
缺點:
  • 同步的模組方式不適合不適合在瀏覽器環境中,同步意味著阻塞載入,瀏覽器資源是非同步載入的
  • 不能非阻塞的並行載入多個模組

AMD

AMD規範其實只有一個主要介面 define(id,dependencies,factory),它要在宣告模組的時候指定所有的依賴dependencies,並且還要當做形參傳到factory中,對於依賴的模組提前執行,依賴前置 [javascript]  view plain  copy        在CODE上檢視程式碼片派生到我的程式碼片
  1. define("module", ["dep1", "dep2"], function(d1, d2) {  
  2.   return someExportedValue;  
  3. });  
  4. require(["module", "../file"], function(module, file) { /* ... */ });  
優點:
  • 適合在瀏覽器環境非同步載入
  • 並行載入多個模組
缺點:
  • 提高開發成本,程式碼閱讀和書寫比較困難
  • 不符合通用的模組思維方式,是一種妥協的實現

CMD

CMD規範和AMD相似,儘量保持簡單,並且與CommonJS和NodeJS的Modules規範保持了很大的相容性。 [javascript]  view plain  copy        在CODE上檢視程式碼片派生到我的程式碼片
  1. define(function(require, exports, module) {  
  2.   var $ = require('jquery');  
  3.   var Spinning = require('./spinning');  
  4.   exports.doSomething = ...  
  5.   module.exports = ...  
  6. })  
優點:
  • 依賴就近,延遲執行
  • 很容易在node中執行
缺點:
  • 依賴SPM打包,模組的載入邏輯偏重