前端模塊化開(kāi)發(fā)(AMD和CDM規(guī)范)
JS
2014-8-9
作者:前端工程師小V
瀏覽:34
標(biāo)簽: javascript javascript的依賴(lài)管理 前端模塊化開(kāi)發(fā)
1、AMD和CDM規(guī)范來(lái)歷
前提:
CommonJS 原來(lái)叫 ServerJS,推出 Modules/1.0 規(guī)范后,在 Node.js 等環(huán)境下取得了很不錯(cuò)的實(shí)踐。09年下半年這幫充滿(mǎn)干勁的小伙子們想把 ServerJS 的成功經(jīng)驗(yàn)進(jìn)一步推廣到瀏覽器端,于是將社區(qū)改名叫 CommonJS,同時(shí)激烈爭(zhēng)論 Modules 的下一版規(guī)范。分歧和沖突由此誕生,逐步形成了三大流派:
1.Modules/1.x 流派。這個(gè)觀(guān)點(diǎn)覺(jué)得 1.x 規(guī)范已經(jīng)夠用,只要移植到瀏覽器端就好。要做的是新增 Modules/Transport 規(guī)范,即在瀏覽器上運(yùn)行前,先通過(guò)轉(zhuǎn)換工具將模塊轉(zhuǎn)換為符合 Transport 規(guī)范的代碼。主流代表是服務(wù)端的開(kāi)發(fā)人員。現(xiàn)在值得關(guān)注的有兩個(gè)實(shí)現(xiàn):越來(lái)越火的 component 和走在前沿的 es6 module transpiler。
2.Modules/Async 流派。這個(gè)觀(guān)點(diǎn)覺(jué)得瀏覽器有自身的特征,不應(yīng)該直接用 Modules/1.x 規(guī)范。這個(gè)觀(guān)點(diǎn)下的典型代表是 AMD 規(guī)范及其實(shí)現(xiàn) RequireJS。
3.Modules/2.0 流派。這個(gè)觀(guān)點(diǎn)覺(jué)得瀏覽器有自身的特征,不應(yīng)該直接用 Modules/1.x 規(guī)范,但應(yīng)該盡可能與 Modules/1.x 規(guī)范保持一致。這個(gè)觀(guān)點(diǎn)下的典型代表是 BravoJS 和 FlyScript 的作者。BravoJS 作者對(duì) CommonJS 的社區(qū)的貢獻(xiàn)很大,這份 Modules/2.0-draft 規(guī)范花了很多心思。
FlyScript 的作者提出了 Modules/Wrappings 規(guī)范,這規(guī)范是 CMD 規(guī)范的前身。可惜的是 BravoJS 太學(xué)院派,F(xiàn)lyScript 后來(lái)做了自我閹割,將整個(gè)網(wǎng)站(flyscript.org)下線(xiàn)了。
AMD
異步模塊定義(AMD)的編程接口提供了定義模塊,及異步加載該模塊的依賴(lài)的機(jī)制。它非常適合于使用于瀏覽器環(huán)境,瀏覽器的同步加載模塊機(jī)制會(huì)帶來(lái)性能,可用性,調(diào)試和跨域訪(fǎng)問(wèn)的問(wèn)題。
本規(guī)范只定義了一個(gè)函數(shù) "define",它是全局變量。函數(shù)的描述為:
define(id?, dependencies?, factory);
id
是個(gè)字符串,它指的是定義中模塊的名字,這個(gè)參數(shù)是可選的。如果沒(méi)有提供該參數(shù),模塊的名字應(yīng)該默認(rèn)為模塊
加載器請(qǐng)求的指定腳本的名字。如果提供了該參數(shù),模塊名必須是“頂級(jí)”的和絕對(duì)的(不允許相對(duì)名字)。
dependencies
是個(gè)定義中模塊所依賴(lài)模塊的數(shù)組。依賴(lài)模塊必須根據(jù)模塊的工廠(chǎng)方法優(yōu)先級(jí)執(zhí)行,并且執(zhí)行的結(jié)果應(yīng)該按照依賴(lài)數(shù)組中的位置順序以參數(shù)的形式傳入(定義中模塊的)工廠(chǎng)方法中。
依賴(lài)的模塊名如果是相對(duì)的,應(yīng)該解析為相對(duì)定義中模塊。換句話(huà)來(lái)說(shuō),相對(duì)名解析為相對(duì)與模塊的名字,并非相對(duì)于尋找該模塊的名字的路徑。
本規(guī)范定義了截然不同的三種特殊的依賴(lài)關(guān)鍵字。如果"require","exports", 或 "module"出現(xiàn)在依賴(lài)列表中,參數(shù)應(yīng)該按照CommonJS模塊規(guī)范自由變量去解析。
依賴(lài)參數(shù)是可選的,如果忽略此參數(shù),它應(yīng)該默認(rèn)為["require", "exports", "module"]。然而,如果工廠(chǎng)方法的長(zhǎng)度屬性小于3,加載器會(huì)選擇以函數(shù)的長(zhǎng)度屬性指定的參數(shù)個(gè)數(shù)調(diào)用工廠(chǎng)方法。
Factory
為模塊初始化要執(zhí)行的函數(shù)或?qū)ο蟆H绻麨楹瘮?shù),它應(yīng)該只被執(zhí)行一次。如果是對(duì)象,此對(duì)象應(yīng)該為模塊的輸出值。
如果工廠(chǎng)方法返回一個(gè)值(對(duì)象,函數(shù),或任意強(qiáng)制類(lèi)型轉(zhuǎn)換為true的值),應(yīng)該為設(shè)置為模塊的輸出值。
下面的例子顯示了requirejs如何動(dòng)態(tài)加載模塊。
define(function ( require ) {
var isReady = false, foobar;
require(['foo', 'bar'], function (foo, bar) {
isReady = true;
foobar = foo() + bar();
});
return {
isReady: isReady,
foobar: foobar
};
});
上面代碼所定義的模塊,內(nèi)部加載了foo和bar兩個(gè)模塊,在沒(méi)有加載完成前,isReady屬性值為false,加載完成后就變成了true。因此,可以根據(jù)isReady屬性的值,決定下一步的動(dòng)作。
require.js
config方法,用來(lái)配置require.js運(yùn)行參數(shù)。config方法接受一個(gè)對(duì)象作為參數(shù)。
paths
參數(shù)指定各個(gè)模塊的位置。這個(gè)位置可以是同一個(gè)服務(wù)器上的相對(duì)位置,也可以是外部網(wǎng)址??梢詾槊總€(gè)模塊定義多個(gè)位置,如果第一個(gè)位置加載失敗,則加載第二個(gè)位置,上面的示例就表示如果CDN加載失敗,則加載服務(wù)器上的備用腳本。需要注意的是,指定本地文件路徑時(shí),可以省略文件最后的js后綴名。
baseUrl
參數(shù)指定本地模塊位置的基準(zhǔn)目錄,即本地模塊的路徑是相對(duì)于哪個(gè)目錄的。該屬性通常由require.js加載時(shí)的data-main屬性指定。
shim
有些庫(kù)不是AMD兼容的,這時(shí)就需要指定shim屬性的值。shim可以理解成“墊片”,用來(lái)幫助require.js加載非AMD規(guī)范的庫(kù)。
require.config({
paths: {
"backbone": "vendor/backbone",
"underscore": "vendor/underscore"
},
shim: {
"backbone": {
deps: [ "underscore" ],
exports: "Backbone"
},
"underscore": {
exports: "_"
}
}
});
上面代碼中的backbone和underscore就是非AMD規(guī)范的庫(kù)。shim指定它們的依賴(lài)關(guān)系(backbone依賴(lài)于underscore),以及輸出符號(hào)(backbone為“Backbone”,underscore為“_”)。
CDM
CMD 模塊定義規(guī)范
在 CMD 規(guī)范中,一個(gè)模塊就是一個(gè)文件。代碼的書(shū)寫(xiě)格式如下:
define(factory);
define 是一個(gè)全局函數(shù),用來(lái)定義模塊。
define(factory)
define 接受 factory 參數(shù),factory 可以是一個(gè)函數(shù),也可以是一個(gè)對(duì)象或字符串。
factory為對(duì)象、字符串時(shí),表示模塊的接口就是該對(duì)象、字符串。比如可以如下定義一個(gè) JSON 數(shù)據(jù)模塊:
define({ "foo": "bar" });
factory 為函數(shù)時(shí),表示是模塊的構(gòu)造方法。執(zhí)行該構(gòu)造方法,可以得到模塊向外提供的接口。factory 方法在執(zhí)行時(shí),默認(rèn)會(huì)傳入三個(gè)參數(shù):require、exports 和 module:
define(function(require, exports, module) {
// 模塊代碼
});
require 是一個(gè)方法,接受 模塊標(biāo)識(shí) 作為唯一參數(shù),用來(lái)獲取其他模塊提供的接口。
中間包括兩個(gè)概念
相對(duì)標(biāo)識(shí) 以 . 開(kāi)頭,只出現(xiàn)在模塊環(huán)境中(define 的 factory 方法里面)。相對(duì)標(biāo)識(shí)永遠(yuǎn)相對(duì)當(dāng)前模塊的 URI 來(lái)解析:
頂級(jí)標(biāo)識(shí) 不以點(diǎn)(.)或斜線(xiàn)(/)開(kāi)始, 會(huì)相對(duì)模塊系統(tǒng)的基礎(chǔ)路徑(即 Sea.js 的 base 路徑)來(lái)解析:
define(id?, deps?, factory)
define 也可以接受兩個(gè)以上參數(shù)。
字符串 id 表示模塊標(biāo)識(shí),
數(shù)組 deps 是模塊依賴(lài)。
比如:
define('hello', ['jquery'], function(require, exports, module) {
// 模塊代碼
});
id 和 deps 參數(shù)可以省略
require
require 是 factory 函數(shù)的第一個(gè)參數(shù)。
require.async
1.require.async 來(lái)進(jìn)行條件加載。
2.require 是同步往下執(zhí)行,require.async 則是異步回調(diào)執(zhí)行。require.async 一般用來(lái)加載可延遲異步加載的模塊。
require.resolve(id)
使用模塊系統(tǒng)內(nèi)部的路徑解析機(jī)制來(lái)解析并返回模塊路徑。該函數(shù)不會(huì)加載模塊,只返回解析后的絕對(duì)路徑。
define(function(require, exports) {
console.log(require.resolve('./b'));
// ==> http:///path/to/b.js
});
這可以用來(lái)獲取模塊路徑,一般用在插件環(huán)境或需動(dòng)態(tài)拼接模塊路徑的場(chǎng)景下。
exports
exports 是一個(gè)對(duì)象,用來(lái)向外提供模塊接口。
除了給 exports 對(duì)象增加成員,還可以使用 return 直接向外提供接口。
module
module是一個(gè)對(duì)象,上面存儲(chǔ)了與當(dāng)前模塊相關(guān)聯(lián)的一些屬性和方法。
module.id
String
模塊的唯一標(biāo)識(shí)。
define('id', [], function(require, exports, module) {
// 模塊代碼
});
上面代碼中,define 的第一個(gè)參數(shù)就是模塊標(biāo)識(shí)。
module.uri
String
根據(jù)模塊系統(tǒng)的路徑解析規(guī)則得到的模塊絕對(duì)路徑。
define(function(require, exports, module) {
console.log(module.uri);
// ==> http:///path/to/this/file.js
});
一般情況下(沒(méi)有在 define 中手寫(xiě) id 參數(shù)時(shí)),module.id 的值就是 module.uri,兩者完全相同。
module.exports
Object
當(dāng)前模塊對(duì)外提供的接口。
exports 參數(shù)是 module.exports 對(duì)象的一個(gè)引用。只通過(guò) exports 參數(shù)來(lái)提供接口,有時(shí)無(wú)法滿(mǎn)足開(kāi)發(fā)者的所有需求。
比如當(dāng)模塊的接口是某個(gè)類(lèi)的實(shí)例時(shí),需要通過(guò) module.exports 來(lái)實(shí)現(xiàn):
define(function(require, exports, module) {
// exports 是 module.exports 的一個(gè)引用
console.log(module.exports === exports); // true
// 重新給 module.exports 賦值
module.exports = new SomeClass();
// exports 不再等于 module.exports
console.log(module.exports === exports); // false
});
注意:對(duì) module.exports 的賦值需要同步執(zhí)行,不能放在回調(diào)函數(shù)里。
相關(guān)參考 https://github.com/seajs/seajs/issues/242 CMD 模塊定義規(guī)范
https://github.com/seajs/seajs/issues/588 前端模塊化開(kāi)發(fā)那點(diǎn)歷史
https://github.com/seajs/seajs/issues/258 模塊標(biāo)識(shí)
https://github.com/amdjs/amdjs-api/wiki/AMD-(中文版) AMD
http://javascript./tool/requirejs.html
|