AMD (異步模塊定義Asynchronous Module Definition)格式的最終目的是提供一個當前開發(fā)者能使用的模塊化Javascript方案。它出自于Dojo用XHR+eval的實踐經(jīng)驗,這種格式的支持者想在以后的項目中避免忍受過去的這些弱點。
AMD模塊格式本身是模塊定義的一個建議,通過它模塊本身和模塊之間的引用可以被異步的加載。它有幾個明顯的優(yōu)點,包括異步的調(diào)用和本身的高擴展性,它實現(xiàn)了解耦,模塊在代碼中也可通過識別號進行查找。當前許多開發(fā)者都喜歡使用它,并且認為它朝ES Harmony提出模塊化系統(tǒng) 邁出了堅實的一步。
最開始AMD在CommonJs的列表中是作為模塊化格式的一個草案,但是由于它不能達到與模塊化完全一致,更進一步的開發(fā)被移到了在amdjs組中。
現(xiàn)在,它包含工程Dojo、MooTools、Firebug以及jQuery。盡管有時你會看見CommonJS AMD 格式化術語,但最好的和它相關的是AMD或者是異步模塊支持,同樣不是所有參與到CommonJS列表的成員都希望與它產(chǎn)生關系。
注意:曾有一段時間涉及Transport/C模塊的提議規(guī)劃沒有面向已經(jīng)存在的CommonJS模塊,但是對于定義模塊來說,它對選擇AMD命名空間約定產(chǎn)生了影響。
關于AMD值得特別注意的兩個概念就是:一個幫助定義模塊的define方法和一個處理依賴加載的require方法。define被用來通過下面的方式定義命名的或者未命名的模塊:
define(
module_id /*可選的*/,
[dependencies] /*可選的*/,
definition function /*用來實例化模塊或者對象的方法*/
);
通過代碼中的注釋我們可以發(fā)現(xiàn),module_id 是可選的,它通常只有在使用非AMD連接工具的時候才是必須的(可能在其它不是特別常見的情況下,它也是有用的)。當不存在module_id參數(shù)的時候,我們稱這個模塊為匿名模塊。
當使用匿名模塊的時候,模塊認定的概念是DRY的,這樣使它在避免文件名和代碼重復的時候顯得很微不足道。因為這樣一來代碼方便切換,你可以很容易地把它移動到其它地方(或者文件系統(tǒng)的其他位置),而不需要更改代碼內(nèi)容或者它的模塊ID。你可以認為模塊id跟文件路徑的概念是相似的。
注意:開發(fā)者們可以將同樣的代碼放到不同的環(huán)境中運行,只要他們使用一個在CommonJS環(huán)境下工作的AMD優(yōu)化器(比如r.js)就可以了。
在回來看define方法簽名, dependencies參數(shù)代表了我們正在定義的模塊需要的dependency數(shù)組,第三個參數(shù)("definition function" or "factory function") 是用來執(zhí)行的初始化模塊的方法。 一個正常的模塊可以像下面那樣定義:
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被用來從一個頂級文件或者模塊里加載代碼,而這是我們原本就希望的動態(tài)加載依賴的位置。它的一個用法如下:
理解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();
});
動態(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: 插件
下面是定義一個兼容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依賴的過程中,要記住,這種方式有一些問題,比如它不完全可能在CSS完全加載的時候建立模塊. 取決于我們?nèi)绾螌崿F(xiàn)創(chuàng)建過程,這也可能導致CSS被作為優(yōu)化文件中的依賴被包含進來,所以在這些情況下把CSS作為已加載的依賴應該多加小心。如果你對上面的做法感興趣,我們也可以從這里查看更多@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();
});
這個例子可以簡單地看出asrequirejs(“app/myModule”,function(){})已被加載到頂層使用。這就展示了通過AMD的define()函數(shù)加載到頂層模塊的不同,下面通過一個本地請求allrequire([])示例兩種類型的裝載機(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兼容的模塊是相當直接的.如上所述,就是在一個數(shù)組中定義任何的模塊依賴作為第一個參數(shù),并且提供回調(diào)函數(shù)來執(zhí)行一次依賴已經(jīng)被加載進來的模塊.例如:
define(["dijit/Tooltip"], function( Tooltip ){
//Our dijit tooltip is now available for local use
new Tooltip(...);
});
請注意模塊的匿名特性,現(xiàn)在它可以在一個Dojo匿名裝載裝置中的被處理,RequireJS或者標準的dojo.require()模塊裝載器。
了解一些有趣的關于模塊引用的陷阱是非常有用的.雖然AMD倡導的引用模塊的方式宣稱它們在一組帶有一些匹配參數(shù)的依賴列表里面,這在版本更老的Dojo 1.6構建系統(tǒng)中并不被支持--它真的僅僅對AMD兼容的裝載器才起作用.例如:
define(["dojo/cookie", "dijit/Tooltip"], function( cookie, Tooltip ){
var cookieValue = cookie( "cookieName" );
new Tooltip(...);
});
越過嵌套的命名空間定義方式有許多好處,模塊不再需要每一次都直接引用完整的命名空間了--所有我們所需要的是依賴中的"dojo/cookie"路徑,它一旦賦給一個作為別名的參數(shù),就可以用變量來引用了.這移除了在我們的應用程序中重復打出"dojo."的必要。
最后需要注意到的難點是,如果我們希望繼續(xù)使用更老的Dojo構建系統(tǒng),或者希望將老版本的模塊遷移到更新的AMD形式,接下來更詳細的版本會使得遷移更加容易.注意dojo和dijit也是作為依賴被引用的:
define(["dojo", "dijit', "dojo/cookie", "dijit/Tooltip"], function( dojo, dijit ){
var cookieValue = dojo.cookie( "cookieName" );
new dijit.Tooltip(...);
});
正如在前面的章節(jié)中,設計模式在提高我們的結(jié)構化構建的共同開發(fā)問題非常有效。 John Hann已經(jīng)給AMD模塊設計模式,涵蓋單例,裝飾,調(diào)解和其他一些優(yōu)秀的設計模式,如果有機會,我強烈建議參考一下他的 幻燈片。 AMD設計模式的選擇可以在下面找到。
一段AMD設計模式可以在下面找到。
修飾設計模式
// mylib/UpdatableObservable: dojo/store/Observable的一個修飾器
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;
};
});
// 修飾器消費者
// mylib/UpdatableObservable的消費者
define(["mylib/UpdatableObservable"], function ( makeUpdatable ) {
var observable,
updatable,
someItem;
// 讓observable 儲存 updatable
updatable = makeUpdatable( observable ); // `new` 關鍵字是可選的!
// 如果我們想傳遞修改過的data,我們要調(diào)用.update()
//updatable.updated( updatedItem );
});
適配器設計模式
// "mylib/Array" 適配`each`方法來模仿 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
});
}
});
});
// 適配器消費者
// "myapp/my-module":
define(["mylib/Array"], function ( array ) {
array.each( ["uno", "dos", "tres"], function ( i, esp ) {
// here, `this` == item
});
});
不像Dojo,jQuery真的存在于一個文件中,而是基于插件機制的庫,我們可以在下面代碼中證明AMD模塊是如何直線前進的。
define(["js/jquery.js","js/jquery.color.js","js/underscore.js"],
function( $, colorPlugin, _ ){
// <span></span>這里,我們通過jQuery中,顏色的插件,并強調(diào)沒有這些將可在全局范圍內(nèi)訪問,但我們可以很容易地在下面引用它們。
// 偽隨機一系列的顏色,在改組后的數(shù)組中選擇的第一個項目
<div>
</div>
var shuffleColor = _.first( _.shuffle( "#666","#333","#111"] ) );
// 在頁面上有class為"item" 的元素隨機動畫改變背景色
$( ".item" ).animate( {"backgroundColor": shuffleColor } );
// 我們的返回可以被其他模塊使用
return {};
});
然而,這個例子中缺失了一些東西,它只是注冊的概念。
jQuery1.7中落實的一個關鍵特性是支持將jQuery當做一個異步兼容的模塊注冊。有很多兼容的腳本加載器(包括RequireJS 和 curl)可以使用異步模塊形式加載模塊,而這意味著在讓事物起作用的時候,更少的需要使用取巧的特殊方法。
如果開發(fā)者想要使用AMD,并且不想將他們的jQuery的版本泄露到全局空間中,他們就應該在使用了jQuery的頂層模塊中調(diào)用noConflict方法.另外,由于多個版本的jQuery可能在一個頁面上,AMD加載器就必須作出特殊的考慮,以便jQuery只使用那些認識到這些問題的AMD加載器來進行注冊,這是使用加載器特殊的define.amd.jQuery來表示的。RequireJS和curl是兩個這樣做了的加載器。
這個叫做AMD的家伙提供了一種安全的魯棒的封包,這個封包可以用于絕大多數(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>
標簽解決方案相比較,非常清晰。有一個清晰的方式用于聲明獨立的模塊,以及它們所依賴的模塊。注意:上面的很多說法也可以說做事YUI模塊加載策略。
相關閱讀
有哪些腳本加載器或者框架支持AMD?
瀏覽器端:
(and more)
服務器端:
在很多項目中使用過AMD,我的結(jié)論就是AMD符合了很多條一個構建嚴肅應用的開發(fā)者所想要的一個好的模塊的格式要求。不用擔心全局,支持命名模塊,不需要服務端轉(zhuǎn)換來工作,在依賴管理中也很方便。
同時也是使用Bacbon.js,ember.js 或者其它結(jié)構化框架來開發(fā)模塊時的利器,可以保持項目的組織架構。 在Dojo和CommonJS世界中,AMD已經(jīng)被討論了兩年了,我們直到它需要時間去逐漸成熟和進化。我們也知道在外面有很多大公司也在實戰(zhàn)中使用了AMD用于構建非凡的系統(tǒng)(IBM, BBC iPlayer),如果它不好,那么可能現(xiàn)在它們就已經(jīng)被丟棄了,但是沒有。
但是,AMD依然有很多地方有待改善。使用這些格式一段時間的開發(fā)者可能已經(jīng)感受到了AMD 樣板和封裝代碼很討厭。盡管我也有這樣的憂慮,但是已經(jīng)存在一些工具例如Volo 可以幫助我們繞過這些問題,同時我也要說整體來看,AMD的優(yōu)勢遠遠勝過其缺點。
更多建議: