AMD規範與CMD規範的區別是什麼?
在比較之前,我們得先來了解下什麼是AMD規範?什麼是CMD規範?當然先申明一下,我個人也是總結下而已,也是網上看到的資料,自己總結下或者可以說整理下而已,供大家更深入的瞭解!因為我們都知道 AMD規範:是 RequireJS 在推廣過程中對模組定義的規範化產出的,而CMD規範:是SeaJS 在推廣過程中對模組定義的規範化產出的。
什麼是CMD規範?
在CMD中 一個模組就是一個檔案,如下程式碼所示:
//基本格式如:define(id, deps, factory) // 比如如下程式碼 define('hello',['jQuery'],function(require, exports, module) { // 模組程式碼 });
define是一個全域性函式,主要是用來定於模組的。其中如上'hello'就是模組名稱,['jquery']是依賴項,也可以依賴於多項可以如下寫法['jquery','',''],分別用逗號隔開,其中上面的id(模組名稱)和deps(被依賴項) 是可以省略的。省略時,那麼模組名稱就是檔名稱。比如我有個檔案叫a.js,那麼我定義模組時候如下程式碼所示:
define(function(require, exports, module) { // 模組程式碼 });
那麼如果我想在b.js程式碼裡面要依賴於a.js的話,那麼我可以直接這樣寫:
define(function(require,exports,module){ var a = require('a')});
但是注意:帶有id 和 deps 是不屬於CMD規範的。所以在seaJS裡面 一般的寫法是不帶模組名稱和依賴項的。就是如上的程式碼格式。
下面看看 factory 在seajs裡面 factory既可以是函式,物件或者字串。factory為物件 字串時候,表示該模組的介面就是該物件或者字串,如下可以定義一個json物件。
define({"aa":'bb'});和jsonp格式類似,不過這樣的資料物件是高度可用的,而且因為是靜態物件,他也是CDN友好的,可以提高效能,比如說我們 有個省市區這麼一個jSON格式要返回我們前端,如果以傳統JSONP的形式提供給客戶端,它必須提供一個callback函式名,根據這個函式名動態生成返回資料,這使得標準JSONP資料一定不是CDN友好的。那麼我們可以利用這種格式
define({
provinces: [
{
name: '上海',
areas: ['浦東新區', '徐彙區']},
{
name: '江蘇',
cities: ['南京', '南通']}
//.....
]
});
假設這個檔名為china.js,那麼如果某個模組需要這個資料,只需要:
define(function(require,exports,module){
var china = require('./china');
//在這裡使用中國省市資料
});
當factory為函式時,表示該模組的構造方法,執行該構造方法,可以得到模組向外提供的介面。預設會傳入三個引數require,exports,module.那麼我們先來看看require引數吧!
require:
require是一個方法,他可以解決依賴,用於獲取其他模組提供的介面,比如下面的a.js程式碼如下
define(function(require, exports) {
exports.a = function(){
// 很多程式碼
};
});
那麼現在我想在b.js裡面呼叫a.js裡面的a方法。我們可以如下做:
define(function(require,exports){
var fun = require('./a');
console.log(fun.a()); // 就可以呼叫到及執行a函式了。
})
require.async(id,callback) : require.async: 方法用來在模組內部非同步載入模組,並在載入完成後執行指定回撥。callback引數可選。比如如下程式碼:
define(function(require, exports, module) { // 非同步載入一個模組,在載入完成時,執行回撥 require.async('./b', function(b) { b.doSomething(); }); // 非同步載入多個模組,在載入完成時,執行回撥 require.async(['./c', './d'], function(c, d) { c.doSomething(); d.doSomething(); }); });
注意: require是同步往下執行的,而require.async 則是非同步回撥執行。
require.resolve(id)
使用模組系統內部的路徑解析機制來解析並返回模組路徑。該函式不會載入模組,只返回解析後的絕對路徑。
define(function(require, exports) { console.log(require.resolve('./b')); // ==> http://example.com/path/to/b.js });
exports
exports:是一個物件,用來向外提供模組介面。
比如還是上面的程式碼:如下a.js裡面
define(function(require, exports) { exports.a = function(){ // 很多程式碼 }; });
或者如下書寫:
define(function(require, exports) { return { i: 'a', a: function(){ // 執行相應的程式碼 } } });
那麼如果我在b.js裡面想要呼叫a.js裡面的a方法,由於a.js使用exports對外提供了介面a方法,那麼在b.js裡面 我們只需要先這樣 var fun = require('./a');然後執行fun.a();就可以執行a方法了。
module
module 是一個物件,上面儲存了與當前模組相關聯的一些屬性和方法。其中exports是module.exports的一個引用。
moudle.id
模組的唯一標識。如下程式碼:
define('id', [], function(require, exports, module) {
// 模組程式碼
});
module.uri
根據模組系統的路徑解析規則得到的模組絕對路徑。
define(function(require, exports, module) {
console.log(module.uri);
// ==> http://example.com/path/to/this/file.js
});
等等屬性 具體可以看seajs官網。原理是一樣的 因為seajs也採用的是CMD規範。
什麼是AMD規範?
如下官網程式碼:
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) { exports.verb = function() { return beta.verb(); //或者: return require("beta").verb(); } });
這裡的require函式讓你能夠隨時去依賴一個模組,即取得模組的引用,從而即使模組沒有作為引數定義,也能夠被使用;exports是定義的 alpha 模組的實體,在其上定義的任何屬性和方法也就是alpha模組的屬性和方法。通過exports.verb = ...就是為alpha模組定義了一個verb方法。例子中是簡單呼叫了模組beta的verb方法。
其中上面的模組名稱和依賴項 也可以省略,那麼如果省略的話,那麼我們可以稱他們為匿名函式,那麼同樣 模組名稱就是檔名稱 和上面的CMD規範類似。上面的['',''] 引數代表了一組對所定義的模組來說必須的依賴項。第三個引數('definition function')是一個用來為你的模組執行初始化的函式。一個最簡單的模組可以以如下方式定義:
// 模組定義函式 // 依賴項(foo 和 bar)被對映為函式的引數 define('myMoudle',['foo','bar'],function(foo,bar){ // 返回一個定義了模組匯出介面的值 // (也就是我們想要匯出後進行呼叫的功能) // 在這裡建立模組 var myModule = { doStuff:function(){ console.log('Yay! Stuff'); } } return myModule; });
還可以這樣書寫:
// 另一個例子可以是... define('myModule',['math', 'graph'], function ( math, graph ) { // 請注意這是一個和 AMD 有些許不同的模式,但用幾種不同的方式 // 來定義模組也是可以的,因為語法在某些方面還是比較靈活的 return { plot: function(x, y){ return graph.drawPie(math.randomGrid(x,y)); } } }; });
另一方面,require 則主要用來在頂層 JavaScript 檔案中或須要動態讀取依賴時載入程式碼。用法的一個例項如下:
// 假設 'foo' 和 'bar' 是兩個外部模組 // 在本例中,這兩個模組被載入後的 'exports' 被當做兩個引數傳遞到了回撥函式中 // 所以可以像這樣來訪問他們 require(['foo', 'bar'], function ( foo, bar ) { // 這裡寫其餘的程式碼 foo.doSomething(); });
動態載入的依賴項 如下程式碼:
define(function ( require ) { var isReady = false, foobar; // 請注意在模組定義內部內聯的 require 語句 require(['foo', 'bar'], function (foo, bar) { isReady = true; foobar = foo() + bar(); }); // 我們仍可以返回一個模組 return { isReady: isReady, foobar: foobar }; });
CMD規範與AMD規範的區別如下:
1. CMD依賴就近:比如如下程式碼
define(function(require,exports,module){ var a = require('./a'); a.doSomthing(); });
程式碼在執行時,首先是不知道依賴的,需要遍歷所有的require關鍵字,找出後面的依賴。具體做法是將function toString後,用正則匹配出require關鍵字後面的依賴。
而AMD依賴前置:如下程式碼:
define(['./a','./b'],function(a,b){ //...... a.doSomthing(); //...... b.doSomthing(); })
程式碼在一旦執行到此處,能立即知曉依賴。而無需遍歷整個函式體找到它的依賴,因此效能有所提升,缺點就是開發者必須顯式得指明依賴——這會使得開發工作量變大,比如:當依賴項有n個時候 那麼寫起來比較煩 且容易出錯。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。
2. 執行順序上:
CMD是延遲執行的,而AMD是提前執行的。
3. api設計角度:
AMD 的 API 預設是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一。比如 AMD 裡,require 分全域性 require 和區域性 require,都叫 require。CMD 裡,沒有全域性 require,而是根據模組系統的完備性,提供 seajs.use 來實現模組系統的載入啟動。CMD 裡,每個 API 都簡單純粹。