AMD (異步模塊定義Asynchronous Module Definition)格式的最終目的是提供一個(gè)當(dāng)前開(kāi)發(fā)者能使用的模塊化Javascript方案。它出自于Dojo用XHR+eval的實(shí)踐經(jīng)驗(yàn),這種格式的支持者想在以后的項(xiàng)目中避免忍受過(guò)去的這些弱點(diǎn)。
AMD模塊格式本身是模塊定義的一個(gè)建議,通過(guò)它模塊本身和模塊之間的引用可以被異步的加載。它有幾個(gè)明顯的優(yōu)點(diǎn),包括異步的調(diào)用和本身的高擴(kuò)展性,它實(shí)現(xiàn)了解耦,模塊在代碼中也可通過(guò)識(shí)別號(hào)進(jìn)行查找。當(dāng)前許多開(kāi)發(fā)者都喜歡使用它,并且認(rèn)為它朝ES Harmony提出模塊化系統(tǒng) 邁出了堅(jiān)實(shí)的一步。
最開(kāi)始AMD在CommonJs的列表中是作為模塊化格式的一個(gè)草案,但是由于它不能達(dá)到與模塊化完全一致,更進(jìn)一步的開(kāi)發(fā)被移到了在amdjs組中。
現(xiàn)在,它包含工程Dojo、MooTools、Firebug以及jQuery。盡管有時(shí)你會(huì)看見(jiàn)CommonJS AMD 格式化術(shù)語(yǔ),但最好的和它相關(guān)的是AMD或者是異步模塊支持,同樣不是所有參與到CommonJS列表的成員都希望與它產(chǎn)生關(guān)系。
注意:曾有一段時(shí)間涉及Transport/C模塊的提議規(guī)劃沒(méi)有面向已經(jīng)存在的CommonJS模塊,但是對(duì)于定義模塊來(lái)說(shuō),它對(duì)選擇AMD命名空間約定產(chǎn)生了影響。
關(guān)于AMD值得特別注意的兩個(gè)概念就是:一個(gè)幫助定義模塊的define方法和一個(gè)處理依賴加載的require方法。define被用來(lái)通過(guò)下面的方式定義命名的或者未命名的模塊:
define(
module_id /*可選的*/,
[dependencies] /*可選的*/,
definition function /*用來(lái)實(shí)例化模塊或者對(duì)象的方法*/
);
通過(guò)代碼中的注釋我們可以發(fā)現(xiàn),module_id 是可選的,它通常只有在使用非AMD連接工具的時(shí)候才是必須的(可能在其它不是特別常見(jiàn)的情況下,它也是有用的)。當(dāng)不存在module_id參數(shù)的時(shí)候,我們稱這個(gè)模塊為匿名模塊。
當(dāng)使用匿名模塊的時(shí)候,模塊認(rèn)定的概念是DRY的,這樣使它在避免文件名和代碼重復(fù)的時(shí)候顯得很微不足道。因?yàn)檫@樣一來(lái)代碼方便切換,你可以很容易地把它移動(dòng)到其它地方(或者文件系統(tǒng)的其他位置),而不需要更改代碼內(nèi)容或者它的模塊ID。你可以認(rèn)為模塊id跟文件路徑的概念是相似的。
注意:開(kāi)發(fā)者們可以將同樣的代碼放到不同的環(huán)境中運(yùn)行,只要他們使用一個(gè)在CommonJS環(huán)境下工作的AMD優(yōu)化器(比如r.js)就可以了。
在回來(lái)看define方法簽名, dependencies參數(shù)代表了我們正在定義的模塊需要的dependency數(shù)組,第三個(gè)參數(shù)("definition function" or "factory function") 是用來(lái)執(zhí)行的初始化模塊的方法。 一個(gè)正常的模塊可以像下面那樣定義:
Understanding AMD: define()
// A module_id (myModule) is used here for demonstration purposes only
define( "myModule",
["foo", "bar"],
// module definition function
// dependencies (foo and bar) are mapped to function parameters
function ( foo, bar ) {
// return a value that defines the module export
// (i.e the functionality we want to expose for consumption)
// create your module here
var myModule = {
doStuff:function () {
console.log( "Yay! Stuff" );
}
};
return myModule;
});
// An alternative version could be..
define( "myModule",
["math", "graph"],
function ( math, graph ) {
// Note that this is a slightly different pattern
// With AMD, it's possible to define modules in a few
// different ways due to it's flexibility with
// certain aspects of the syntax
return {
plot: function( x, y ){
return graph.drawPie( math.randomGrid( x, y ) );
}
};
});
另一方面,require被用來(lái)從一個(gè)頂級(jí)文件或者模塊里加載代碼,而這是我們?cè)揪拖M膭?dòng)態(tài)加載依賴的位置。它的一個(gè)用法如下:
理解AMD: require()
// Consider "foo" and "bar" are two external modules
// In this example, the "exports" from the two modules
// loaded are passed as function arguments to the
// callback (foo and bar) so that they can similarly be accessed
require(["foo", "bar"], function ( foo, bar ) {
// rest of your code here
foo.doSomething();
});
動(dòng)態(tài)加載依賴
define(function ( require ) {
var isReady = false, foobar;
// note the inline require within our module definition
require(["foo", "bar"], function ( foo, bar ) {
isReady = true;
foobar = foo() + bar();
});
// we can still return a module
return {
isReady: isReady,
foobar: foobar
};
});
理解 AMD: 插件
下面是定義一個(gè)兼容AMD插件的例子:
// With AMD, it's possible to load in assets of almost any kind
// including text-files and HTML. This enables us to have template
// dependencies which can be used to skin components either on
// page-load or dynamically.
define( ["./templates", "text!./template.md","css!./template.css" ],
function( templates, template ){
console.log( templates );
// do something with our templates here
}
});
注意:盡管上面的例子中css!被包含在在加載CSS依賴的過(guò)程中,要記住,這種方式有一些問(wèn)題,比如它不完全可能在CSS完全加載的時(shí)候建立模塊. 取決于我們?nèi)绾螌?shí)現(xiàn)創(chuàng)建過(guò)程,這也可能導(dǎo)致CSS被作為優(yōu)化文件中的依賴被包含進(jìn)來(lái),所以在這些情況下把CSS作為已加載的依賴應(yīng)該多加小心。如果你對(duì)上面的做法感興趣,我們也可以從這里查看更多@VIISON的RequireJS CSS 插件:https://github.com/VIISON/RequireCSS
使用RequireJS加載AMD模塊
require(["app/myModule"],
function( myModule ){
// start the main module which in-turn
// loads other modules
var module = new myModule();
module.doStuff();
});
這個(gè)例子可以簡(jiǎn)單地看出asrequirejs(“app/myModule”,function(){})已被加載到頂層使用。這就展示了通過(guò)AMD的define()函數(shù)加載到頂層模塊的不同,下面通過(guò)一個(gè)本地請(qǐng)求allrequire([])示例兩種類型的裝載機(jī)(curl.js和RequireJS)。
使用curl.js加載AMD模塊
curl(["app/myModule.js"],
function( myModule ){
// start the main module which in-turn
// loads other modules
var module = new myModule();
module.doStuff();
});
延遲依賴模塊
// This could be compatible with jQuery's Deferred implementation,
// futures.js (slightly different syntax) or any one of a number
// of other implementations
define(["lib/Deferred"], function( Deferred ){
var defer = new Deferred();
require(["lib/templates/?index.html","lib/data/?stats"],
function( template, data ){
defer.resolve( { template: template, data:data } );
}
);
return defer.promise();
});
使用Dojo定義AMD兼容的模塊是相當(dāng)直接的.如上所述,就是在一個(gè)數(shù)組中定義任何的模塊依賴作為第一個(gè)參數(shù),并且提供回調(diào)函數(shù)來(lái)執(zhí)行一次依賴已經(jīng)被加載進(jìn)來(lái)的模塊.例如:
define(["dijit/Tooltip"], function( Tooltip ){
//Our dijit tooltip is now available for local use
new Tooltip(...);
});
請(qǐng)注意模塊的匿名特性,現(xiàn)在它可以在一個(gè)Dojo匿名裝載裝置中的被處理,RequireJS或者標(biāo)準(zhǔn)的dojo.require()模塊裝載器。
了解一些有趣的關(guān)于模塊引用的陷阱是非常有用的.雖然AMD倡導(dǎo)的引用模塊的方式宣稱它們?cè)谝唤M帶有一些匹配參數(shù)的依賴列表里面,這在版本更老的Dojo 1.6構(gòu)建系統(tǒng)中并不被支持--它真的僅僅對(duì)AMD兼容的裝載器才起作用.例如:
define(["dojo/cookie", "dijit/Tooltip"], function( cookie, Tooltip ){
var cookieValue = cookie( "cookieName" );
new Tooltip(...);
});
越過(guò)嵌套的命名空間定義方式有許多好處,模塊不再需要每一次都直接引用完整的命名空間了--所有我們所需要的是依賴中的"dojo/cookie"路徑,它一旦賦給一個(gè)作為別名的參數(shù),就可以用變量來(lái)引用了.這移除了在我們的應(yīng)用程序中重復(fù)打出"dojo."的必要。
最后需要注意到的難點(diǎn)是,如果我們希望繼續(xù)使用更老的Dojo構(gòu)建系統(tǒng),或者希望將老版本的模塊遷移到更新的AMD形式,接下來(lái)更詳細(xì)的版本會(huì)使得遷移更加容易.注意dojo和dijit也是作為依賴被引用的:
define(["dojo", "dijit', "dojo/cookie", "dijit/Tooltip"], function( dojo, dijit ){
var cookieValue = dojo.cookie( "cookieName" );
new dijit.Tooltip(...);
});
正如在前面的章節(jié)中,設(shè)計(jì)模式在提高我們的結(jié)構(gòu)化構(gòu)建的共同開(kāi)發(fā)問(wèn)題非常有效。 John Hann已經(jīng)給AMD模塊設(shè)計(jì)模式,涵蓋單例,裝飾,調(diào)解和其他一些優(yōu)秀的設(shè)計(jì)模式,如果有機(jī)會(huì),我強(qiáng)烈建議參考一下他的 幻燈片。 AMD設(shè)計(jì)模式的選擇可以在下面找到。
一段AMD設(shè)計(jì)模式可以在下面找到。
修飾設(shè)計(jì)模式
// mylib/UpdatableObservable: dojo/store/Observable的一個(gè)修飾器
define(["dojo", "dojo/store/Observable"], function ( dojo, Observable ) {
return function UpdatableObservable ( store ) {
var observable = dojo.isFunction( store.notify ) ? store :
new Observable(store);
observable.updated = function( object ) {
dojo.when( object, function ( itemOrArray) {
dojo.forEach( [].concat(itemOrArray), this.notify, this );
});
};
return observable;
};
});
// 修飾器消費(fèi)者
// mylib/UpdatableObservable的消費(fèi)者
define(["mylib/UpdatableObservable"], function ( makeUpdatable ) {
var observable,
updatable,
someItem;
// 讓observable 儲(chǔ)存 updatable
updatable = makeUpdatable( observable ); // `new` 關(guān)鍵字是可選的!
// 如果我們想傳遞修改過(guò)的data,我們要調(diào)用.update()
//updatable.updated( updatedItem );
});
適配器設(shè)計(jì)模式
// "mylib/Array" 適配`each`方法來(lái)模仿 jQuerys:
define(["dojo/_base/lang", "dojo/_base/array"], function ( lang, array ) {
return lang.delegate( array, {
each: function ( arr, lambda ) {
array.forEach( arr, function ( item, i ) {
lambda.call( item, i, item ); // like jQuery's each
});
}
});
});
// 適配器消費(fèi)者
// "myapp/my-module":
define(["mylib/Array"], function ( array ) {
array.each( ["uno", "dos", "tres"], function ( i, esp ) {
// here, `this` == item
});
});
不像Dojo,jQuery真的存在于一個(gè)文件中,而是基于插件機(jī)制的庫(kù),我們可以在下面代碼中證明AMD模塊是如何直線前進(jìn)的。
define(["js/jquery.js","js/jquery.color.js","js/underscore.js"],
function( $, colorPlugin, _ ){
// <span></span>這里,我們通過(guò)jQuery中,顏色的插件,并強(qiáng)調(diào)沒(méi)有這些將可在全局范圍內(nèi)訪問(wèn),但我們可以很容易地在下面引用它們。
// 偽隨機(jī)一系列的顏色,在改組后的數(shù)組中選擇的第一個(gè)項(xiàng)目
<div>
</div>
var shuffleColor = _.first( _.shuffle( "#666","#333","#111"] ) );
// 在頁(yè)面上有class為"item" 的元素隨機(jī)動(dòng)畫改變背景色
$( ".item" ).animate( {"backgroundColor": shuffleColor } );
// 我們的返回可以被其他模塊使用
return {};
});
然而,這個(gè)例子中缺失了一些東西,它只是注冊(cè)的概念。
jQuery1.7中落實(shí)的一個(gè)關(guān)鍵特性是支持將jQuery當(dāng)做一個(gè)異步兼容的模塊注冊(cè)。有很多兼容的腳本加載器(包括RequireJS 和 curl)可以使用異步模塊形式加載模塊,而這意味著在讓事物起作用的時(shí)候,更少的需要使用取巧的特殊方法。
如果開(kāi)發(fā)者想要使用AMD,并且不想將他們的jQuery的版本泄露到全局空間中,他們就應(yīng)該在使用了jQuery的頂層模塊中調(diào)用noConflict方法.另外,由于多個(gè)版本的jQuery可能在一個(gè)頁(yè)面上,AMD加載器就必須作出特殊的考慮,以便jQuery只使用那些認(rèn)識(shí)到這些問(wèn)題的AMD加載器來(lái)進(jìn)行注冊(cè),這是使用加載器特殊的define.amd.jQuery來(lái)表示的。RequireJS和curl是兩個(gè)這樣做了的加載器。
這個(gè)叫做AMD的家伙提供了一種安全的魯棒的封包,這個(gè)封包可以用于絕大多數(shù)情況。
// Account for the existence of more than one global
// instances of jQuery in the document, cater for testing
// .noConflict()
var jQuery = this.jQuery || "jQuery",
$ = this.$ || "$",
originaljQuery = jQuery,
original$ = $;
define(["jquery"] , function ( $ ) {
$( ".items" ).css( "background","green" );
return function () {};
});
<script>
標(biāo)簽解決方案相比較,非常清晰。有一個(gè)清晰的方式用于聲明獨(dú)立的模塊,以及它們所依賴的模塊。注意:上面的很多說(shuō)法也可以說(shuō)做事YUI模塊加載策略。
相關(guān)閱讀
有哪些腳本加載器或者框架支持AMD?
瀏覽器端:
(and more)
服務(wù)器端:
在很多項(xiàng)目中使用過(guò)AMD,我的結(jié)論就是AMD符合了很多條一個(gè)構(gòu)建嚴(yán)肅應(yīng)用的開(kāi)發(fā)者所想要的一個(gè)好的模塊的格式要求。不用擔(dān)心全局,支持命名模塊,不需要服務(wù)端轉(zhuǎn)換來(lái)工作,在依賴管理中也很方便。
同時(shí)也是使用Bacbon.js,ember.js 或者其它結(jié)構(gòu)化框架來(lái)開(kāi)發(fā)模塊時(shí)的利器,可以保持項(xiàng)目的組織架構(gòu)。 在Dojo和CommonJS世界中,AMD已經(jīng)被討論了兩年了,我們直到它需要時(shí)間去逐漸成熟和進(jìn)化。我們也知道在外面有很多大公司也在實(shí)戰(zhàn)中使用了AMD用于構(gòu)建非凡的系統(tǒng)(IBM, BBC iPlayer),如果它不好,那么可能現(xiàn)在它們就已經(jīng)被丟棄了,但是沒(méi)有。
但是,AMD依然有很多地方有待改善。使用這些格式一段時(shí)間的開(kāi)發(fā)者可能已經(jīng)感受到了AMD 樣板和封裝代碼很討厭。盡管我也有這樣的憂慮,但是已經(jīng)存在一些工具例如Volo 可以幫助我們繞過(guò)這些問(wèn)題,同時(shí)我也要說(shuō)整體來(lái)看,AMD的優(yōu)勢(shì)遠(yuǎn)遠(yuǎn)勝過(guò)其缺點(diǎn)。
更多建議: