AMD和CMD規範
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 的模塊
define("alpha", [ "require", "exports", "beta" ], function( require, exports, beta ){ export.verb = function(){ return beta.verb(); // or: return require("beta").verb(); } });一個返回對象字面量的異步模塊
define(["alpha"], function( alpha ){ return { verb : function(){ return alpha.verb() + 1 ; } } });
無依賴模塊可以直接使用對象字面量來定義
define( { add : function( x, y ){ return x + y ; } } );
require(); 在 AMD 規範中的 require 函數與一般的 CommonJS中的 require 不同。由於動態檢測依賴關系使加載異步,對於基於回調的 require 需求強烈。
局部 與 全局 的require
局部的 require 需要在AMD模式中的 define 工廠函數中傳入 require。define( [‘require‘], function( require ){ // ... } ); or: define( function( require, exports, module ){ // ... } );
局部的 require 需要其他特定的 API 來實現。 全局的 require 函數是唯一全局作用域下的變量,像 define一樣。全局的 require 並不是規範要求的,但是如果實現全局的 require函數,那麽其需要具有與局部 require 函數 一樣的以下的限定: 1. 模塊標識視為絕對的,而不是相對的對應另一個模塊標識。 2. 只有在異步情況下,require的回調方式才被用來作為交互操作使用。因為他不可能在同步的情況下通過 require(String) 從頂層加載模塊。 依賴相關的API會開始模塊加載。如果需要有互操作的多個加載器,那麽全局的 reqiure 應該被加載頂層模塊來代替。
require(String) define( function( require ){ var a = require(‘a‘); // 加載模塊a } ); require(Array, Function) define( function( require ){ require( [‘a‘, ‘b‘], function( a,b ){ // 加載模塊a b 使用 // 依賴 a b 模塊的運行代碼 } ); } ); require.toUrl( Url ) define( function( require ){ var temp = require.toUrl(‘./temp/a.html‘); // 加載頁面 } );
define 和 require 這兩個定義模塊,調用模塊的方法合稱為AMD模式,定義模塊清晰,不會汙染全局變量,清楚的顯示依賴關系。AMD模式可以用於瀏覽器環境並且允許非同步加載模塊,也可以按需動態加載模塊。 在CMD中,一個模塊就是一個文件,格式為: define( factory ); 全局函數define,用來定義模塊。 參數 factory 可以是一個函數,也可以為對象或者字符串。 當 factory 為對象、字符串時,表示模塊的接口就是該對象、字符串。 定義JSON數據模塊:
define({ "foo": "bar" });
通過字符串定義模板模塊:
define(‘this is {{data}}.‘);
factory 為函數的時候,表示模塊的構造方法,執行構造方法便可以得到模塊向外提供的接口。
define( function(require, exports, module) { // 模塊代碼 });
define( id?, deps?, factory ); define也可以接受兩個以上的參數,字符串id為模塊標識,數組deps為模塊依賴:
define( ‘module‘, [‘module1‘, ‘module2‘], function( require, exports, module ){ // 模塊代碼 } );
其與 AMD 規範用法不同。 require 是 factory 的第一個參數。 require( id ); 接受模塊標識作為唯一的參數,用來獲取其他模塊提供的接口:
define(function( require, exports ){ var a = require(‘./a‘); a.doSomething(); });
require.async( id, callback? ); require是同步往下執行的,需要的異步加載模塊可以使用 require.async 來進行加載:
define( function(require, exports, module) { require.async(‘.a‘, function(a){ a.doSomething(); }); });
require.resolve( id ) 可以使用模塊內部的路徑機制來返回模塊路徑,不會加載模塊。 exports 是 factory 的第二個參數,用來向外提供模塊接口。
define(function( require, exports ){ exports.foo = ‘bar‘; // 向外提供的屬性 exports.do = function(){}; // 向外提供的方法 });
當然也可以使用 return 直接向外提供接口。
define(function( require, exports ){ return{ foo : ‘bar‘, // 向外提供的屬性 do : function(){} // 向外提供的方法 } });
也可以簡化為直接對象字面量的形式:
define({ foo : ‘bar‘, // 向外提供的屬性 do : function(){} // 向外提供的方法 });
與nodeJS中一樣需要註意的是,一下方式是錯誤的:
define(function( require, exports ){ exports = { foo : ‘bar‘, // 向外提供的屬性 do : function(){} // 向外提供的方法 } });
需要這麽做
define(function( require, exports, module ){ module.exports = { foo : ‘bar‘, // 向外提供的屬性 do : function(){} // 向外提供的方法 } });
傳入的對象引用可以添加屬性,一旦賦值一個新的對象,那麽值錢傳遞進來的對象引用就會失效了。開始之初,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()來導入其他模塊的輸出到當前模塊。 例子:// moduleA.js module.exports = function( value ){ return value * 2; }
// moduleB.js var multiplyBy2 = require(‘./moduleA‘); var result = multiplyBy2(4);
CommonJS是同步加載模塊,但其實也有瀏覽器端的實現,其原理是將所有模塊都定義好並通過id進行索引,這樣就可以瀏覽器進行解析了
服務器端的Node.js遵循CommonJS規範。核心思想是允許模塊通過require 方法來同步加載所要依賴的其他模塊,然後通過 exports或module.exports來導出需要暴露的接口。
require("module"); require("../file.js"); exports.doStuff = function() {}; module.exports = someValue;優點:
- 服務器端便於重用
- NPM中已經將近20w個模塊包
- 簡單並容易使用
- 同步的模塊方式不適合不適合在瀏覽器環境中,同步意味著阻塞加載,瀏覽器資源是異步加載的
- 不能非阻塞的並行加載多個模塊
AMD
AMD規範其實只有一個主要接口 define(id,dependencies,factory),它要在聲明模塊的時候指定所有的依賴dependencies,並且還要當做形參傳到factory中,對於依賴的模塊提前執行,依賴前置define("module", ["dep1", "dep2"], function(d1, d2) { return someExportedValue; }); require(["module", "../file"], function(module, file) { /* ... */ });優點:
- 適合在瀏覽器環境異步加載
- 並行加載多個模塊
- 提高開發成本,代碼閱讀和書寫比較困難
- 不符合通用的模塊思維方式,是一種妥協的實現
CMD
CMD規範和AMD相似,盡量保持簡單,並且與CommonJS和NodeJS的Modules規範保持了很大的兼容性。define(function(require, exports, module) { var $ = require(‘jquery‘); var Spinning = require(‘./spinning‘); exports.doSomething = ... module.exports = ... })
優點:
- 依賴就近,延遲執行
- 很容易在node中運行
- 依賴SPM打包,模塊的加載邏輯偏重
轉載:https://www.zhihu.com/question/20351507
http://blog.csdn.net/vuturn/article/details/51970567
AMD和CMD規範