# AMD
## 在瀏覽器中編寫模塊化Javascript的格式
AMD (異步模塊定義Asynchronous Module Definition)格式的最終目的是提供一個當前開發者能使用的模塊化Javascript方案。它出自于Dojo用XHR+eval的實踐經驗,這種格式的支持者想在以后的項目中避免忍受過去的這些弱點。
AMD模塊格式本身是模塊定義的一個建議,通過它模塊本身和模塊之間的引用可以被異步的加載。它有幾個明顯的優點,包括異步的調用和本身的高擴展性,它實現了解耦,模塊在代碼中也可通過識別號進行查找。當前許多開發者都喜歡使用它,并且認為它朝ES Harmony提出模塊化系統 邁出了堅實的一步。
最開始AMD在CommonJs的列表中是作為模塊化格式的一個草案,但是由于它不能達到與模塊化完全一致,更進一步的開發被移到了在amdjs組中。
現在,它包含工程Dojo、MooTools、Firebug以及jQuery。盡管有時你會看見CommonJS AMD 格式化術語,但最好的和它相關的是AMD或者是異步模塊支持,同樣不是所有參與到CommonJS列表的成員都希望與它產生關系。
> 注意:曾有一段時間涉及Transport/C模塊的提議規劃沒有面向已經存在的CommonJS模塊,但是對于定義模塊來說,它對選擇AMD命名空間約定產生了影響。
## 從模塊開始
關于AMD值得特別注意的兩個概念就是:一個幫助定義模塊的define方法和一個處理依賴加載的require方法。define被用來通過下面的方式定義命名的或者未命名的模塊:
~~~
define(
module_id /*可選的*/,
[dependencies] /*可選的*/,
definition function /*用來實例化模塊或者對象的方法*/
);
~~~
通過代碼中的注釋我們可以發現,module_id 是可選的,它通常只有在使用非AMD連接工具的時候才是必須的(可能在其它不是特別常見的情況下,它也是有用的)。當不存在module_id參數的時候,我們稱這個模塊為匿名模塊。
當使用匿名模塊的時候,模塊認定的概念是DRY的,這樣使它在避免文件名和代碼重復的時候顯得很微不足道。因為這樣一來代碼方便切換,你可以很容易地把它移動到其它地方(或者文件系統的其他位置),而不需要更改代碼內容或者它的模塊ID。你可以認為模塊id跟文件路徑的概念是相似的。
注意:開發者們可以將同樣的代碼放到不同的環境中運行,只要他們使用一個在CommonJS環境下工作的AMD優化器(比如r.js)就可以了。
在回來看define方法簽名, dependencies參數代表了我們正在定義的模塊需要的dependency數組,第三個參數("definition function" or "factory function") 是用來執行的初始化模塊的方法。 一個正常的模塊可以像下面那樣定義:
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被用來從一個頂級文件或者模塊里加載代碼,而這是我們原本就希望的動態加載依賴的位置。它的一個用法如下:
理解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();
});
~~~
動態加載依賴
~~~
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完全加載的時候建立模塊. 取決于我們如何實現創建過程,這也可能導致CSS被作為優化文件中的依賴被包含進來,所以在這些情況下把CSS作為已加載的依賴應該多加小心。如果你對上面的做法感興趣,我們也可以從這里查看更多@VIISON的RequireJS CSS 插件:[https://github.com/VIISON/RequireCSS](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()函數加載到頂層模塊的不同,下面通過一個本地請求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模塊
使用Dojo定義AMD兼容的模塊是相當直接的.如上所述,就是在一個數組中定義任何的模塊依賴作為第一個參數,并且提供回調函數來執行一次依賴已經被加載進來的模塊.例如:
~~~
define(["dijit/Tooltip"], function( Tooltip ){
//Our dijit tooltip is now available for local use
new Tooltip(...);
});
~~~
請注意模塊的匿名特性,現在它可以在一個Dojo匿名裝載裝置中的被處理,RequireJS或者標準的dojo.require()模塊裝載器。
了解一些有趣的關于模塊引用的陷阱是非常有用的.雖然AMD倡導的引用模塊的方式宣稱它們在一組帶有一些匹配參數的依賴列表里面,這在版本更老的Dojo 1.6構建系統中并不被支持--它真的僅僅對AMD兼容的裝載器才起作用.例如:
~~~
define(["dojo/cookie", "dijit/Tooltip"], function( cookie, Tooltip ){
var cookieValue = cookie( "cookieName" );
new Tooltip(...);
});
~~~
越過嵌套的命名空間定義方式有許多好處,模塊不再需要每一次都直接引用完整的命名空間了--所有我們所需要的是依賴中的"dojo/cookie"路徑,它一旦賦給一個作為別名的參數,就可以用變量來引用了.這移除了在我們的應用程序中重復打出"dojo."的必要。
最后需要注意到的難點是,如果我們希望繼續使用更老的Dojo構建系統,或者希望將老版本的模塊遷移到更新的AMD形式,接下來更詳細的版本會使得遷移更加容易.注意dojo和dijit也是作為依賴被引用的:
~~~
define(["dojo", "dijit', "dojo/cookie", "dijit/Tooltip"], function( dojo, dijit ){
var cookieValue = dojo.cookie( "cookieName" );
new dijit.Tooltip(...);
});
~~~
## AMD 模塊設計模式 (Dojo)
正如在前面的章節中,設計模式在提高我們的結構化構建的共同開發問題非常有效。 John Hann已經給AMD模塊設計模式,涵蓋單例,裝飾,調解和其他一些優秀的設計模式,如果有機會,我強烈建議參考一下他的 幻燈片。 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,我們要調用.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
});
});
~~~
## 使用jQuery的AMD模塊
不像Dojo,jQuery真的存在于一個文件中,而是基于插件機制的庫,我們可以在下面代碼中證明AMD模塊是如何直線前進的。
~~~
define(["js/jquery.js","js/jquery.color.js","js/underscore.js"],
function( $, colorPlugin, _ ){
// <span></span>這里,我們通過jQuery中,顏色的插件,并強調沒有這些將可在全局范圍內訪問,但我們可以很容易地在下面引用它們。
// 偽隨機一系列的顏色,在改組后的數組中選擇的第一個項目
<div>
</div>
var shuffleColor = _.first( _.shuffle( "#666","#333","#111"] ) );
// 在頁面上有class為"item" 的元素隨機動畫改變背景色
$( ".item" ).animate( {"backgroundColor": shuffleColor } );
// 我們的返回可以被其他模塊使用
return {};
});
~~~
然而,這個例子中缺失了一些東西,它只是注冊的概念。
## 將jQuery當做一個異步兼容的模塊注冊
jQuery1.7中落實的一個關鍵特性是支持將jQuery當做一個異步兼容的模塊注冊。有很多兼容的腳本加載器(包括RequireJS 和 curl)可以使用異步模塊形式加載模塊,而這意味著在讓事物起作用的時候,更少的需要使用取巧的特殊方法。
如果開發者想要使用AMD,并且不想將他們的jQuery的版本泄露到全局空間中,他們就應該在使用了jQuery的頂層模塊中調用noConflict方法.另外,由于多個版本的jQuery可能在一個頁面上,AMD加載器就必須作出特殊的考慮,以便jQuery只使用那些認識到這些問題的AMD加載器來進行注冊,這是使用加載器特殊的define.amd.jQuery來表示的。RequireJS和curl是兩個這樣做了的加載器。
這個叫做AMD的家伙提供了一種安全的魯棒的封包,這個封包可以用于絕大多數情況。
~~~
// 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 () {};
});
~~~
### 為什么AMD是寫模塊化Javascript代碼的好幫手呢?
* 提供了一個清晰的方案,告訴我們如何定義一個可擴展的模塊。
* 和我們常用的前面的全局命名空間以及?`<script>`?標簽解決方案相比較,非常清晰。有一個清晰的方式用于聲明獨立的模塊,以及它們所依賴的模塊。
* 模塊定義被封裝了,有助于我們避免污染全局命名空間。
* 比其它替代方案能更好的工作(例如CommonJS,后面我們就會看到)。沒有跨域問題,局部以及調試問題,不依賴于服務器端工具。大多數AMD加載器支持在瀏覽器中加載模塊,而不需要構建過程。
* 提供一個“透明”的方法用于在單個文件中包含多個模塊。其它方式像 CommonJS 要求必須遵循一個傳輸格式。 再有需要的時候,可以惰性加載腳本。
> 注意:上面的很多說法也可以說做事YUI模塊加載策略。
相關閱讀
* [The RequireJS Guide To AMD](http://requirejs.org/docs/whyamd.html)
* [What's the fastest way to load AMD modules?](http://unscriptable.com/2011/09/21/what-is-the-fastest-way-to-load-amd-modules/)
* [AMD vs. CommonJS, what's the better format?](http://unscriptable.com/2011/09/30/amd-versus-cjs-whats-the-best-format/)
* [AMD Is Better For The Web Than CommonJS Modules](http://blog.millermedeiros.com/amd-is-better-for-the-web-than-commonjs-modules/)
* [The Future Is Modules Not Frameworks](http://unscriptable.com/code/Modules-Frameworks/#0)
* [AMD No Longer A CommonJS Specification](http://groups.google.com/group/commonjs/browse_thread/thread/96a0963bcb4ca78f/cf73db49ce2)
* [On Inventing JavaScript Module Formats And Script Loaders](http://tagneto.blogspot.com/2011/04/on-inventing-js-module-formats-and.html)
* [The AMD Mailing List](http://groups.google.com/group/amd-implement)
有哪些腳本加載器或者框架支持AMD?
瀏覽器端:
* [RequireJS](http://requirejs.org/)
* [curl.js](http://github.com/unscriptable/curl)
* [bdLoad](http://bdframework.com/bdLoad)
* [Yabble](http://github.com/jbrantly/yabble)
* [PINF](http://github.com/pinf/loader-js)
(and more)
服務器端:
* [RequireJS](http://requirejs.org/)
* [PINF](http://github.com/pinf/loader-js)
## AMD 總結
在很多項目中使用過AMD,我的結論就是AMD符合了很多條一個構建嚴肅應用的開發者所想要的一個好的模塊的格式要求。不用擔心全局,支持命名模塊,不需要服務端轉換來工作,在依賴管理中也很方便。
同時也是使用Bacbon.js,ember.js 或者其它結構化框架來開發模塊時的利器,可以保持項目的組織架構。 在Dojo和CommonJS世界中,AMD已經被討論了兩年了,我們直到它需要時間去逐漸成熟和進化。我們也知道在外面有很多大公司也在實戰中使用了AMD用于構建非凡的系統(IBM, BBC iPlayer),如果它不好,那么可能現在它們就已經被丟棄了,但是沒有。
但是,AMD依然有很多地方有待改善。使用這些格式一段時間的開發者可能已經感受到了AMD 樣板和封裝代碼很討厭。盡管我也有這樣的憂慮,但是已經存在一些工具例如Volo 可以幫助我們繞過這些問題,同時我也要說整體來看,AMD的優勢遠遠勝過其缺點。
- 前言
- 簡介
- 什么是設計模式?
- 設計模式的結構
- 編寫設計模式
- 反模式
- 設計模式的分類
- 設計模式分類概覽表
- JavaScript 設計模式
- 構造器模式
- 模塊化模式
- 暴露模塊模式
- 單例模式
- 觀察者模式
- 中介者模式
- 原型模式
- 命令模式
- 外觀模式
- 工廠模式
- Mixin 模式
- 裝飾模式
- 亨元(Flyweight)模式
- JavaScript MV* 模式
- MVC 模式
- MVP 模式
- MVVM 模式
- 最新的模塊化 JavaScript 設計模式
- AMD
- CommonJS
- ES Harmony
- JQuery 中的設計模式
- 組合模式
- 適配器模式
- 外觀模式
- 觀察者模式
- 迭代器模式
- 惰性初始模式
- 代理模式
- 建造者模式
- jQuery 插件的設計模式
- JavaScript 命名空間模式
- 總結
- 參考