CommonJS, AMD and CMD

1.為什么需要模塊化

本段轉(zhuǎn)自https://segmentfault.com/a/1190000002591834合敦。
模塊化開發(fā)在編程開發(fā)中是一個(gè)非常重要的概念,一個(gè)優(yōu)秀的模塊化項(xiàng)目的后期維護(hù)成本可以大大降低验游。本文主要介紹JavaScript模塊化開發(fā)的那些事充岛,文中通過(guò)一個(gè)小故事比較直觀地闡述了模塊化開發(fā)的過(guò)程。

小A是某個(gè)創(chuàng)業(yè)團(tuán)隊(duì)的前端工程師耕蝉,負(fù)責(zé)編寫項(xiàng)目的Javascript程序崔梗。

全局變量沖突

根據(jù)自己的經(jīng)驗(yàn),小A先把一些常用的功能抽出來(lái)垒在,寫成函數(shù)放到一個(gè)公用文件base.js中:

var _ = {
    $: function(id) { return document.getElementById(id); },
    getCookie: function(key) { ... },
    setCookie: function(key, value) { ... }
}

小A把這些函數(shù)都放在_對(duì)象內(nèi)蒜魄,以防過(guò)多的全局變量造成沖突。他告訴團(tuán)隊(duì)的其他成員场躯,如果誰(shuí)想使用這些函數(shù)权悟,只要引入base.js就可以了。

小C是小A的同事推盛,他向小A反映:自己的頁(yè)面引入了一個(gè)叫做underscore.js的類庫(kù),而且谦铃,這個(gè)類庫(kù)也會(huì)占用這個(gè)全局變量耘成,這樣一來(lái)就會(huì)跟base.js中的沖突了。小A心想驹闰,underscore.js是第三方類庫(kù)瘪菌,估計(jì)不好改,但是base.js已經(jīng)在很多頁(yè)面鋪開嘹朗,不可能改师妙。最后小A只好無(wú)奈地把underscore.js占用的全局變量改了。

此時(shí)屹培,小A發(fā)現(xiàn)默穴,把函數(shù)都放在一個(gè)名字空間內(nèi)怔檩,可以減少全局變量沖突的概率,卻沒有解決全局變量沖突這個(gè)問(wèn)題蓄诽。

依賴

隨著業(yè)務(wù)的發(fā)展薛训,小A又編寫了一系列的函數(shù)庫(kù)和UI組件,比方說(shuō)標(biāo)簽切換組件tabs.js仑氛,此組件需調(diào)用base.js以及util.js中的函數(shù)乙埃。

有一天,新同事小D跟小A反映锯岖,自己已經(jīng)在頁(yè)面中引用了tabs.js介袜,功能卻不正常。小A一看就發(fā)現(xiàn)問(wèn)題了出吹,原來(lái)小D不知道tabs.js依賴于base.js以及util.js遇伞,他并沒有添加這兩個(gè)文件的引用。于是趋箩,他馬上進(jìn)行修改:

<script src="tabs.js"></script>
<script src="base.js"></script>
<script src="util.js"></script>

然而赃额,功能還是不正常,此時(shí)小A教訓(xùn)小D說(shuō):“都說(shuō)是依賴叫确,那被依賴方肯定要放在依賴方之前啊”跳芳。原來(lái)小D把base.js和util.js放到tabs.js之后了。

小A心想竹勉,他是作者飞盆,自然知道組件的依賴情況,但是別人就難說(shuō)了次乓,特別是新人吓歇。

過(guò)了一段時(shí)間,小A給標(biāo)簽切換組件增加了功能票腰,為了實(shí)現(xiàn)這個(gè)功能城看,tabs.js還需要調(diào)用ui.js中的函數(shù)。這時(shí)杏慰,小A發(fā)現(xiàn)了一個(gè)嚴(yán)重的問(wèn)題测柠,他需要在所有調(diào)用了tabs.js的頁(yè)面上增加ui.js的引用!T道摹轰胁!

又過(guò)了一段時(shí)間,小A優(yōu)化了tabs.js朝扼,這個(gè)組件已經(jīng)不再依賴于util.js赃阀,所以他在所有用到tabs.js的頁(yè)面中移除了util.js的引用,以提高性能擎颖。他這一修改榛斯,出大事了观游,測(cè)試組MM告訴他,有些頁(yè)面不正常了肖抱。小A一看备典,恍然大悟,原來(lái)某些頁(yè)面的其他功能用到了util.js中的函數(shù)意述,他把這個(gè)文件的引用去掉導(dǎo)致出錯(cuò)了提佣。為了保證功能正常,他又把代碼恢復(fù)了荤崇。

小A又想拌屏,有沒有辦法在修改依賴的同時(shí)不用逐一修改頁(yè)面,也不影響其他功能呢术荤?

模塊化

小A逛互聯(lián)網(wǎng)的時(shí)候倚喂,無(wú)意中發(fā)現(xiàn)了一種新奇的模塊化編碼方式,可以把它之前遇到的問(wèn)題全部解決瓣戚。

在模塊化編程方式下端圈,每個(gè)文件都是一個(gè)模塊。每個(gè)模塊都由一個(gè)名為define的函數(shù)創(chuàng)建子库。例如舱权,把base.js改造成一個(gè)模塊后,代碼會(huì)變成這樣:

define(function(require, exports, module) {
    exports.$ = function(id) { return document.getElementById(id); };
    exports.getCookie = function(key) { ... };
    exports.setCookie = function(key, value) { ... };
});

base.js向外提供的接口都被添加到exports這個(gè)對(duì)象仑嗅。而exports是一個(gè)局部變量宴倍,整個(gè)模塊的代碼都沒有占用半個(gè)全局變量。

那如何調(diào)用某個(gè)模塊提供的接口呢仓技?以tabs.js為例鸵贬,它要依賴于base.js和util.js:

define(function(require, exports, module) {
    var _ = require('base.js'), util = require('util.js');
    var div_tabs = _.$('tabs');
    // .... 其他代碼
});

一個(gè)模塊可以通過(guò)局部函數(shù)require獲取其他模塊的接口。此時(shí)脖捻,變量和util都是局部變量阔逼,并且,變量名完全是受開發(fā)者控制的地沮,如果你不喜歡颜价,那也可以用base:

define(function(require, exports, module) {
    var base = require('base.js'), util = require('util.js');
    var div_tabs = base.$('tabs');
    // .... 其他代碼
});

一旦要移除util.js、添加ui.js诉濒,那只要修改tabs.js就可以了:

define(function(require, exports, module) {
    var base = require('base.js'), ui = require('ui.js');
    var div_tabs = base.$('tabs');
    // .... 其他代碼
});

注意:關(guān)于“模塊化”都是基于開發(fā)階段的,應(yīng)用階段不存在模塊化的概念夕春。

2.CommonJS

commonjs是一個(gè)模塊化的規(guī)范未荒。在CommonJS中,定義了一個(gè)全局性方法require()及志,用于加載模塊片排。假定有一個(gè)數(shù)學(xué)模塊math.js寨腔,就可以像下面這樣加載。

var math = require('math');

然后率寡,就可以調(diào)用模塊提供的方法:

  var math = require('math');
        math.add(2,3); // 5

CommonJS定義的模塊分為:{模塊引用(require)} {模塊定義(exports)} {模塊標(biāo)識(shí)(module)}

require()用來(lái)引入外部模塊迫卢;exports對(duì)象用于導(dǎo)出當(dāng)前模塊的方法或變量,唯一的導(dǎo)出口冶共;module對(duì)象就代表模塊本身乾蛤。如node.js的模塊化就是基于commonjs,還有browserify等捅僵。

3.AMD

基于commonJS規(guī)范的nodeJS出來(lái)以后家卖,服務(wù)端的模塊概念已經(jīng)形成,很自然地庙楚,大家就想要客戶端模塊上荡。而且最好兩者能夠兼容,一個(gè)模塊不用修改馒闷,在服務(wù)器和瀏覽器都可以運(yùn)行酪捡。但是,由于一個(gè)重大的局限纳账,使得CommonJS規(guī)范不適用于瀏覽器環(huán)境逛薇。還是上面的代碼,如果在瀏覽器中運(yùn)行塞祈,會(huì)有一個(gè)很大的問(wèn)題金刁,你能看出來(lái)嗎?

 var math = require('math');
    math.add(2, 3);

第二行math.add(2, 3)议薪,在第一行require('math')之后運(yùn)行尤蛮,因此必須等math.js加載完成。也就是說(shuō)斯议,如果加載時(shí)間很長(zhǎng)产捞,整個(gè)應(yīng)用就會(huì)停在那里等。您會(huì)注意到** require 是同步的**哼御。
這對(duì)服務(wù)器端不是一個(gè)問(wèn)題坯临,因?yàn)樗械哪K都存放在本地硬盤,可以同步加載完成恋昼,等待時(shí)間就是硬盤的讀取時(shí)間看靠。但是,對(duì)于瀏覽器液肌,這卻是一個(gè)大問(wèn)題挟炬,因?yàn)槟K都放在服務(wù)器端,等待時(shí)間取決于網(wǎng)速的快慢,可能要等很長(zhǎng)時(shí)間谤祖,瀏覽器處于"假死"狀態(tài)婿滓。

因此,瀏覽器端的模塊粥喜,不能采用"同步加載"(synchronous)凸主,只能采用"異步加載"(asynchronous)。這就是AMD規(guī)范誕生的背景额湘。

CommonJS是主要為了JS在后端的表現(xiàn)制定的卿吐,他是不適合前端的,AMD(異步模塊定義)出現(xiàn)了缩挑,它就主要為前端JS的表現(xiàn)制定規(guī)范但两。
AMD是"Asynchronous Module Definition"的縮寫,意思就是"異步模塊定義"供置。它采用異步方式加載模塊谨湘,模塊的加載不影響它后面語(yǔ)句的運(yùn)行。所有依賴這個(gè)模塊的語(yǔ)句芥丧,都定義在一個(gè)回調(diào)函數(shù)中紧阔,等到加載完成之后,這個(gè)回調(diào)函數(shù)才會(huì)運(yùn)行续担。
AMD也采用require()語(yǔ)句加載模塊擅耽,但是不同于CommonJS,它要求兩個(gè)參數(shù):

require([module], callback);

第一個(gè)參數(shù)[module]物遇,是一個(gè)數(shù)組乖仇,里面的成員就是要加載的模塊;第二個(gè)參數(shù)callback询兴,則是加載成功之后的回調(diào)函數(shù)乃沙。如果將前面的代碼改寫成AMD形式,就是下面這樣:

 require(['math','jquery'], function (math,$) {
    math.add(2, 3);
  });

math.add()與math模塊加載不是同步的诗舰,瀏覽器不會(huì)發(fā)生假死警儒。所以很顯然,AMD比較適合瀏覽器環(huán)境眶根。目前蜀铲,主要有兩個(gè)Javascript庫(kù)實(shí)現(xiàn)了AMD規(guī)范:require.jscurl.js.

AMD規(guī)范寫法

假定現(xiàn)在有一個(gè)math.js文件,它定義了一個(gè)math模塊属百。那么记劝,math.js就要這樣寫:

// math.js
  define('math',function (){
    var add = function (x,y){
      return x+y;
    };
    return {
      add: add
    };
  });

加載方法如下:

 // main.js
  require(['math'], function (math){
    alert(math.add(1,1));
  });

如果這個(gè)模塊還依賴其他模塊,那么define()函數(shù)的第一個(gè)參數(shù)族扰,必須是一個(gè)數(shù)組隆夯,指明該模塊的依賴性

  define('模塊名',['myLib'], function(myLib){
    function foo(){
      myLib.doSomething();
    }
    return {
      foo : foo
    };
  });

4.CMD

大名遠(yuǎn)揚(yáng)的玉伯寫了seajs钳恕,就是遵循他提出的CMD規(guī)范,與AMD蠻相近的蹄衷,不過(guò)用起來(lái)感覺更加方便些,最重要的是中文版厘肮,應(yīng)有盡有:seajs官方doc

define(function(require,exports,module){...});

用過(guò)seajs吧愧口,這個(gè)不陌生吧,對(duì)吧类茂。

前面說(shuō)AMD耍属,說(shuō)RequireJS實(shí)現(xiàn)了AMD,CMD看起來(lái)與AMD好像呀巩检,那RequireJS與SeaJS像不像呢厚骗?

雖然CMD與AMD蠻像的,但區(qū)別還是挺明顯的兢哭,官方非官方都有闡述和理解领舰,我覺得吧,說(shuō)的都挺好:
官方闡述SeaJS與RequireJS異同
SeaJS與RequireJS的最大異同(這個(gè)說(shuō)的也挺好)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末迟螺,一起剝皮案震驚了整個(gè)濱河市冲秽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌矩父,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異颈走,居然都是意外死亡敞峭,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門球订,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)后裸,“玉大人,你說(shuō)我怎么就攤上這事辙售∏岜В” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵旦部,是天一觀的道長(zhǎng)祈搜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)士八,這世上最難降的妖魔是什么容燕? 我笑而不...
    開封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮婚度,結(jié)果婚禮上蘸秘,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好醋虏,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開白布寻咒。 她就那樣靜靜地躺著,像睡著了一般颈嚼。 火紅的嫁衣襯著肌膚如雪毛秘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天阻课,我揣著相機(jī)與錄音叫挟,去河邊找鬼。 笑死限煞,一個(gè)胖子當(dāng)著我的面吹牛抹恳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播署驻,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼奋献,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了硕舆?” 一聲冷哼從身側(cè)響起秽荞,我...
    開封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抚官,沒想到半個(gè)月后扬跋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凌节,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年钦听,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倍奢。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朴上,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出卒煞,到底是詐尸還是另有隱情痪宰,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布畔裕,位于F島的核電站衣撬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏扮饶。R本人自食惡果不足惜具练,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望甜无。 院中可真熱鬧扛点,春花似錦哥遮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至铜邮,卻和暖如春君仆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牲距。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钥庇,地道東北人牍鞠。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像评姨,于是被迫代替她去往敵國(guó)和親难述。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容