[TOC]
## 簡介
AMD 規范在這里:
中文:https://github.com/amdjs/amdjs-api/wiki/AMD-(%E4%B8%AD%E6%96%87%E7%89%88)
英文:http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition
AMD提出了一種基于模塊的異步加載JavaScript代碼的機制,它推薦開發人員將JavaScript代碼封裝進一個個模塊,對全局對象的依 賴變成了對其他模塊的依賴,無須再聲明一大堆的全局變量。
通過延遲和按需加載來解決各個模塊的依賴關系。模塊化的JavaScript代碼好處很明顯,各 個功能組件的松耦合性可以極大的提升代碼的復用性、可維護性。
這種非阻塞式的并發式快速加載JavaScript代碼,使Web頁面上其他不依賴 JavaScript代碼的UI元素,如圖片、CSS以及其他DOM節點得以先加載完畢,Web頁面加載速度更快,用戶也得到更好的體驗。
## CMD 推崇異步加載,遵循依賴前置(加載前置)(提前加載)
CMD 推崇依賴就近,AMD 推崇依賴前置。看代碼:
```js
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此處略去 100 行
var b = require('./b') // 依賴可以就近書寫
b.doSomething()
// ...
})
// AMD 默認推薦的是
define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好
a.doSomething()
// 此處略去 100 行
b.doSomething()
//...
})
```
雖然 AMD 也支持 CMD 的寫法,同時還支持將 require 作為依賴項傳遞,但 RequireJS 的作者默認是最喜歡上面的寫法,也是官方文檔里默認的模塊定義寫法。
## define方法:定義模塊
作為一個規范,只需定義其語法API,而不關心其實現。CommonJS的AMD規范中只定義了一個全局的方法,即`define`函數:
```js
define([module-name?], [array-of-dependencies?], [module-factory-or-object]);
```
其中:
1. `module-name`: 模塊標識,為文件名(沒有js后綴),可以省略。如果提供了該參數,模塊名必須是“頂級”的和絕對的(不允許相對名字)。
2. `array-of-dependencies`:
是一個字符串Array,表示該模塊依賴的其他所有模塊標識,模塊依賴必須在真正執行具體的factory方法前解決,這 些依賴對象加載執行以后的返回值,可以以默認的順序作為factory方法的參數。`array-of-dependencies` 也是可選參數,當用戶不提供該參數時,實現 AMD的框架應提供默認值為`[“require”,”exports”,“module”]`。所依賴的模塊,可以省略。
3. `module-factory-or-object`: 模塊的實現,或者一個JavaScript對象。
`module-name` 遵循 [CommonJS Module Identifiers](http://wiki.commonjs.org/wiki/Modules/1.1.1#Module_Identifiers) 。array-of-dependencies 元素的順序和 module-factory-or-object 參數一一對應。
從中可以看到,第一個參數和第二個參數都是可以省略的,第三個參數則是模塊的具體實現本身。后面將介紹在不同的應用場景下,他們會使用不同的參數組合。
CommonJS在規范中并沒有詳細規定其他的方法(只有define函數),一些主要的AMD框架如RequireJS、curl、bdload等都實現了define方法,同時各個框架都有自己的補充使得其API更實用。
## 加載模塊的require方法(全局require)
實際中,我們經常會遇到一些阻塞模塊加載的依賴,如果交互次數很多,需要大量的模塊加載,應該采用**全局依賴**的形式去加載頂層模塊。
require方法用于調用模塊。它的參數與define方法類似。
```js
require(['foo', 'bar'], function ( foo, bar ) {
foo.doSomething();
});
```
上面方法表示加載foo和bar兩個模塊,當這兩個模塊都加載成功后,執行一個回調函數。該回調函數就用來完成具體的任務。
require方法的第一個參數,是一個表示依賴關系的數組。這個數組可以寫得很靈活,請看下面的例子。
```js
require( [ window.JSON ? undefined : 'util/json2' ], function ( JSON ) {
JSON = JSON || window.JSON;
console.log( JSON.parse( '{ "JSON" : "HERE" }' ) );
});
```
上面代碼加載JSON模塊時,首先判斷瀏覽器是否原生支持JSON對象。如果是的,則將undefined傳入回調函數,否則加載util目錄下的json2模塊。
require方法也可以用在define方法內部。
```js
define(function (require) {
var otherModule = require('otherModule');
});
```
下面的例子顯示了如何動態加載模塊。
```js
define(function ( require ) {
var isReady = false, foobar;
require(['foo', 'bar'], function (foo, bar) {
isReady = true;
foobar = foo() + bar();
});
return {
isReady: isReady,
foobar: foobar
};
});
```
上面代碼所定義的模塊,內部加載了foo和bar兩個模塊,在沒有加載完成前,isReady屬性值為false,加載完成后就變成了true。因此,可以根據isReady屬性的值,決定下一步的動作。
下面的例子是模塊的輸出結果是一個promise對象。
```js
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();
});
```
上面代碼的define方法返回一個promise對象,可以在該對象的then方法,指定下一步的動作。
如果服務器端采用JSONP模式,則可以直接在require中調用,方法是指定JSONP的callback參數為define。
```js
require( [
"http://someapi.com/foo?callback=define"
], function (data) {
console.log(data);
});
```
require方法允許添加第三個參數,即錯誤處理的回調函數。
```js
require(
[ "backbone" ],
function ( Backbone ) {
return Backbone.View.extend({ /* ... */ });
},
function (err) {
// ...
}
);
```
require方法的第三個參數,即處理錯誤的回調函數,接受一個error對象作為參數。
require對象還允許指定一個全局性的Error事件的監聽函數。所有沒有被上面的方法捕獲的錯誤,都會被觸發這個監聽函數。
```js
requirejs.onError = function (err) {
// ...
};
```
`define`和`require`在依賴處理和回調執行上都是一樣的,不一樣的地方是define的回調函數需要有return語句返回模塊對象,這樣`define`定義的模塊才能被其他模塊引用;`require`的回調函數不需要return語句。
## 局部require
局部require可以被解析成一個符合AMD工廠函數規范的require函數。
例如:
```
define(['require'], function (require) {
// the require in here is a local require.
});
define(function (require, exports, module) {
// the require in here is a local require.
});
```
局部require也支持其他標準實現的API:
```js
define( function( require ){
var a = require('a'); // 加載模塊a
} );
define( function( require ){
require( ['a', 'b'], function( a,b ){ // 加載模塊a b 使用
// 依賴 a b 模塊的運行代碼
} );
} );
define( function( require ){
var temp = require.toUrl('./temp/a.html'); // 加載頁面
} );
```
## AMD實例:如何定義一個模塊
下面代碼定義了一個alpha模塊,并且依賴于內置的require,exports模塊,以及外部的beta模塊。可以看到,第三個參數是回調函數,可以直接使用依賴的模塊,他們按依賴聲明順序作為參數提供給回調函數。
這里的require函數讓你能夠隨時去依賴一個模塊,即取得模塊的引用,從而即使模塊沒有作為參數定義,也能夠被使用;exports是定義的alpha 模塊的實體,在其上定義的任何屬性和方法也就是alpha模塊的屬性和方法。通過`exports.verb = ...`就是為alpha模塊定義了一個`verb`方法。
例子中是簡單調用了模塊beta的verb方法。
```js
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//或者:
return require("beta").verb(); //require函數用來加載一個模塊
}
});
```
## 實際應用
```js
//定義M模塊,本申明一個全局變量
define('M',[],function(){
window.M={};
return M;
})
//定義模塊a 依賴模塊 M,b,c
define('a',['M','b','c'],function(M){
alert(M.ob);
alert(M.oc);
})
//定義b模塊
define('b',[],function(){
M.ob = 2;
return M;
})
//定義c模塊
define('c',[],function(){
M.oc = 3;
return M;
})
//引入a模塊
require(['a'],function(a){
})
```
## CommonJS wrapping
其實是標準的AMD規范里面是完全兼容 CommonJs的,AMD本意是想統一前后端的,現在AMD一般在前端比較多。
為了復用已有的 CommonJS 模塊,AMD 規定了 [Simplified CommonJS wrapping](https://github.com/amdjs/amdjs-api/wiki/AMD#simplified-commonjs-wrapping-),然后 RequireJS 實現了它(先后順序不一定對)。它提供了類似于 CommonJS 的模塊定義方式,如下:
```js
define(function(require, exports, module) {
var A = require('a'); //就近定義
return function () {};
});
```
這樣,模塊的依賴可以像 CommonJS 一樣「就近定義」。但就是這個看上去兩全其美的做法,給大家帶來了很多困擾。
### 困擾
```js
//mod1.js
define(function() {
console.log('require module: mod1');
return {
hello: function() {
console.log("hello mod1");
}
};
});
JS
//mod2.js
define(function() {
console.log('require module: mod2');
return {
hello: function() {
console.log("hello mod2");
}
};
});
//main.js
define(function(require, exports, module) { //CommonJS寫法
//運行至此,mod1.js 和 mod2.js 已經下載完成;
console.log('require module: main');
var mod1 = require('./mod1'); //這里才執行 mod1 ?
mod1.hello();
var mod2 = require('./mod2'); //這里才執行 mod2 ?
mod2.hello();
return {
hello: function() {
console.log('hello main');
}
};
});
```
CommonJS Wrapper 只是書寫上兼容了 CommonJS 的寫法,模塊運行邏輯并不會改變。
因為 main.js 中 mod1 和 mod2 兩個模塊并行加載,且加載完就執行,所以前兩行輸出順序取決于哪個 js 先加載完。
這種「就近」書寫的依賴,非常容易讓人認為 main.js 執行到對應 require 語句時才執行 mod1 或 mod2,但這是錯誤的,因為 CommonJS Wrapper 并不會改變 AMD「盡早執行」依賴的本質!
實際上,對于按需執行依賴的加載器,如 [SeaJS](http://seajs.org/),上述代碼結果一定是:
~~~
require module: main
require module: mod1
hello mod1
require module: mod2
hello mod2
hello main
~~~
于是,了解過 CommonJS 或 CMD 模塊規范的同學,看到使用 CommonJS Wrapper 方式寫的 AMD 模塊,容易產生理解偏差,從而誤認為 RequireJS 有 bug。
我覺得「盡早執行」或「按需執行」兩種策略沒有明顯的優劣之分,但 AMD 這種「模仿別人寫法,卻提供不一樣的特性」這個做法十分愚蠢。
## 具體實現
http://requirejs.org/
https://github.com/cujojs/curl
## 參考
https://imququ.com/post/amd-simplified-commonjs-wrapping.html
https://github.com/amdjs/amdjs-api/wiki
http://www.cnblogs.com/happyPawpaw/archive/2012/05/31/2528864.html
http://www.jianshu.com/p/9b44a1fa8a96
[使用 AMD、CommonJS 及 ES Harmony 編寫模塊化的 JavaScript](http://justineo.github.io/singles/writing-modular-js/)
- 講解 Markdown
- 示例
- SVN
- Git筆記
- github 相關
- DESIGNER'S GUIDE TO DPI
- JS 模塊化
- CommonJS、AMD、CMD、UMD、ES6
- AMD
- RequrieJS
- r.js
- 模塊化打包
- 學習Chrome DevTools
- chrome://inspect
- Chrome DevTools 之 Elements
- Chrome DevTools 之 Console
- Chrome DevTools 之 Sources
- Chrome DevTools 之 Network
- Chrome DevTools 之 Memory
- Chrome DevTools 之 Performance
- Chrome DevTools 之 Resources
- Chrome DevTools 之 Security
- Chrome DevTools 之 Audits
- 技巧
- Node.js
- 基礎知識
- package.json 詳解
- corepack
- npm
- yarn
- pnpm
- yalc
- 庫處理
- Babel
- 相關庫
- 轉譯基礎
- 插件
- AST
- Rollup
- 基礎
- 插件
- Webpack
- 詳解配置
- 實現 loader
- webpack 進階
- plugin 用法
- 輔助工具
- 解答疑惑
- 開發工具集合
- 花樣百出的打包工具
- 紛雜的構建系統
- monorepo
- 前端工作流
- 爬蟲
- 測試篇
- 綜合
- Jest
- playwright
- Puppeteer
- cypress
- webdriverIO
- TestCafe
- 其他
- 工程開發
- gulp篇
- Building With Gulp
- Sass篇
- PostCSS篇
- combo服務
- 編碼規范檢查
- 前端優化
- 優化策略
- 高性能HTML5
- 瀏覽器端性能
- 前后端分離篇
- 分離部署
- API 文檔框架
- 項目開發環境
- 基于 JWT 的 Token 認證
- 扯皮時間
- 持續集成及后續服務
- 靜態服務器搭建
- mock與調試
- browserslist
- Project Starter
- Docker
- 文檔網站生成
- ddd