前端模塊化(CommonJs,AMD和CMD)

前端模塊規(guī)范有三種:CommonJs,AMD和CMD巢钓。
CommonJs用在服務(wù)器端喘批,AMD和CMD用在瀏覽器環(huán)境
AMD 是 RequireJS 在推廣過程中對(duì)模塊定義的規(guī)范化產(chǎn)出裁替。
CMD 是 SeaJS 在推廣過程中對(duì)模塊定義的規(guī)范化產(chǎn)出阀溶。
AMD:提前執(zhí)行(異步加載:依賴先執(zhí)行)+延遲執(zhí)行
CMD:延遲執(zhí)行(運(yùn)行到需加載抽莱,根據(jù)順序執(zhí)行)

模塊

  • 函數(shù)寫法
  function f1(){
    //...
  }
  function f2(){
    //...
  }

模塊就是實(shí)現(xiàn)特定功能的文件葵陵,把幾個(gè)函數(shù)放在一個(gè)文件里就構(gòu)成了一個(gè)模塊。需要的時(shí)候加載這個(gè)文件蘸泻,調(diào)用其中的函數(shù)即可琉苇。
但這樣做會(huì)污染全局變量,無法保證不與其他模塊發(fā)生變量名沖突悦施,而且模塊成員之間沒什么關(guān)系并扇。

  • 對(duì)象寫法
      var module = {
    star : 0,
    f1 : function (){
      //...
    },
    f2 : function (){
      //...
    }
  };
      module.f1();
      module.star = 1;

模塊寫成一個(gè)對(duì)象,模塊成員都封裝在對(duì)象里抡诞,通過調(diào)用對(duì)象屬性穷蛹,訪問使用模塊成員土陪。但同時(shí)也暴露了模塊成員,外部可以修改模塊內(nèi)部狀態(tài)肴熏。

  • 立即執(zhí)行函數(shù)
      var module = (function(){
    var star = 0;
    var f1 = function (){
      console.log('ok');
    };
    var f2 = function (){
      //...
    };
         return {
            f1:f1,
            f2:f2
         };
  })();
      module.f1();  //ok
      console.log(module.star)  //undefined

外部無法訪問內(nèi)部私有變量

CommonJs

CommonJS是服務(wù)器端模塊的規(guī)范鬼雀,由Node推廣使用。由于服務(wù)端編程的復(fù)雜性蛙吏,如果沒有模塊很難與操作系統(tǒng)及其他應(yīng)用程序互動(dòng)源哩。使用方法如下:

math.js
exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
      sum += args[i++];
    }
    return sum;
};

increment.js
var add = require('math').add;
exports.increment = function(val) {
    return add(val, 1);
};

index.js
var increment = require('increment').increment;
var a = increment(1); //2

根據(jù)CommonJS規(guī)范:

  • 一個(gè)單獨(dú)的文件就是一個(gè)模塊。每一個(gè)模塊都是一個(gè)單獨(dú)的作用域鸦做,也就是說励烦,在該模塊內(nèi)部定義的變量,無法被其他模塊讀取泼诱,除非定義為global對(duì)象的屬性坛掠。

  • 輸出模塊變量的最好方法是使用module.exports對(duì)象。

  • 加載模塊使用require方法治筒,該方法讀取一個(gè)文件并執(zhí)行却音,返回文件內(nèi)部的module.exports對(duì)象

仔細(xì)看上面的代碼狈究,您會(huì)注意到 require 是同步的挤巡。模塊系統(tǒng)需要同步讀取模塊文件內(nèi)容赫蛇,并編譯執(zhí)行以得到模塊接口。
然而句灌, 這在瀏覽器端問題多多。

瀏覽器端欠拾,加載 JavaScript 最佳胰锌、最容易的方式是在 document 中插入<script>標(biāo)簽。但腳本標(biāo)簽天生異步藐窄,傳統(tǒng) CommonJS 模塊在瀏覽器環(huán)境中無法正常加載资昧。

解決思路之一是,開發(fā)一個(gè)服務(wù)器端組件荆忍,對(duì)模塊代碼作靜態(tài)分析格带,將模塊與它的依賴列表一起返回給瀏覽器端。 這很好使刹枉,但需要服務(wù)器安裝額外的組件叽唱,并因此要調(diào)整一系列底層架構(gòu)。

另一種解決思路是微宝,用一套標(biāo)準(zhǔn)模板來封裝模塊定義:

define(function(require, exports, module) {

  // The module code goes here

});

這套模板代碼為模塊加載器提供了機(jī)會(huì)棺亭,使其能在模塊代碼執(zhí)行之前,對(duì)模塊代碼進(jìn)行靜態(tài)分析蟋软,并動(dòng)態(tài)生成依賴列表镶摘。

math.js
define(function(require, exports, module) {
  exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
      sum += args[i++];
    }
    return sum;
  };
});

increment.js
define(function(require, exports, module) {
  var add = require('math').add;
  exports.increment = function(val) {
    return add(val, 1);
  };
});

index.js
define(function(require, exports, module) {
  var inc = require('increment').increment;
  inc(1); // 2
});

AMD

AMD是"Asynchronous Module Definition"的縮寫嗽桩,意思就是"異步模塊定義"。由于不是JavaScript原生支持凄敢,使用AMD規(guī)范進(jìn)行頁面開發(fā)需要用到對(duì)應(yīng)的庫函數(shù)碌冶,也就是大名鼎鼎RequireJS,實(shí)際上AMD 是 RequireJS 在推廣過程中對(duì)模塊定義的規(guī)范化的產(chǎn)出

它采用異步方式加載模塊贡未,模塊的加載不影響它后面語句的運(yùn)行种樱。所有依賴這個(gè)模塊的語句,都定義在一個(gè)回調(diào)函數(shù)中俊卤,等到加載完成之后嫩挤,這個(gè)回調(diào)函數(shù)才會(huì)運(yùn)行。

RequireJS主要解決兩個(gè)問題

  • 多個(gè)js文件可能有依賴關(guān)系消恍,被依賴的文件需要早于依賴它的文件加載到瀏覽器
  • js加載的時(shí)候?yàn)g覽器會(huì)停止頁面渲染岂昭,加載文件越多,頁面失去響應(yīng)時(shí)間越長

RequireJs也采用require()語句加載模塊狠怨,但是不同于CommonJS约啊,它要求兩個(gè)參數(shù):

第一個(gè)參數(shù)[module],是一個(gè)數(shù)組佣赖,里面的成員就是要加載的模塊恰矩;第二個(gè)參數(shù)callback,則是加載成功之后的回調(diào)函數(shù)憎蛤。math.add()與math模塊加載不是同步的外傅,瀏覽器不會(huì)發(fā)生假死。

require([module], callback);

require([increment'], function (increment) {
    increment.add(1);
});
define()函數(shù)

RequireJS定義了一個(gè)函數(shù) define俩檬,它是全局變量萎胰,用來定義模塊:
define(id?, dependencies?, factory);
參數(shù)說明:

  • id:指定義中模塊的名字,可選棚辽;如果沒有提供該參數(shù)技竟,模塊的名字應(yīng)該默認(rèn)為模塊加載器請(qǐng)求的指定腳本的名字。如果提供了該參數(shù)屈藐,模塊名必須是“頂級(jí)”的和絕對(duì)的(不允許相對(duì)名字)榔组。

  • 依賴dependencies:是一個(gè)當(dāng)前模塊依賴的,已被模塊定義的模塊標(biāo)識(shí)的數(shù)組字面量联逻。
    依賴參數(shù)是可選的瓷患,如果忽略此參數(shù),它應(yīng)該默認(rèn)為["require", "exports", "module"]遣妥。然而擅编,如果工廠方法的長度屬性小于3,加載器會(huì)選擇以函數(shù)的長度屬性指定的參數(shù)個(gè)數(shù)調(diào)用工廠方法。

  • 工廠方法factory爱态,模塊初始化要執(zhí)行的函數(shù)或?qū)ο筇诽啊H绻麨楹瘮?shù),它應(yīng)該只被執(zhí)行一次锦担。如果是對(duì)象俭识,此對(duì)象應(yīng)該為模塊的輸出值。

來舉個(gè)??看看:

define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
      exports.verb = function() {
          return beta.verb();
          //Or:
          return require("beta").verb();
      }
  });
RequireJs使用例子

require.config是用來定義別名的洞渔,在paths屬性下配置別名套媚。然后通過requirejs(參數(shù)一,參數(shù)二)磁椒;參數(shù)一是數(shù)組堤瘤,傳入我們需要引用的模塊名,第二個(gè)參數(shù)是個(gè)回調(diào)函數(shù)浆熔,回調(diào)函數(shù)傳入一個(gè)變量本辐,代替剛才所引入的模塊。

main.js
//別名配置
requirejs.config({
    paths: {
        jquery: 'jquery.min' //可以省略.js
    }
});
//引入模塊医增,用變量$表示jquery模塊
requirejs(['jquery'], function ($) {
    $('body').css('background-color','red');
});

引入模塊也可以只寫require()慎皱。requirejs通過define()定義模塊,定義的參數(shù)上同叶骨。在此模塊內(nèi)的方法和變量外部是無法訪問的茫多,只有通過return返回才行.

math.js
define('math',['jquery'], function ($) {//引入jQuery模塊
    return {
        add: function(x,y){
            return x + y;
        }
    };
});

將該模塊命名為math.js保存。

require(['jquery','math'], function ($,math) {
    console.log(math.add(10,100));//110
});

main.js引入模塊方法

CMD

CMD 即Common Module Definition通用模塊定義忽刽,CMD規(guī)范是國內(nèi)發(fā)展出來的天揖,就像AMD有個(gè)requireJS,CMD有個(gè)瀏覽器的實(shí)現(xiàn)SeaJS缔恳,SeaJS要解決的問題和requireJS一樣,只不過在模塊定義方式和模塊加載(可以說運(yùn)行洁闰、解析)時(shí)機(jī)上有所不同歉甚。

在 CMD 規(guī)范中,一個(gè)模塊就是一個(gè)文件扑眉。代碼的書寫格式如下:

define(function(require, exports, module) {

  // 模塊代碼

});

require是可以把其他模塊導(dǎo)入進(jìn)來的一個(gè)參數(shù);而exports是可以把模塊內(nèi)的一些屬性和方法導(dǎo)出的;module 是一個(gè)對(duì)象纸泄,上面存儲(chǔ)了與當(dāng)前模塊相關(guān)聯(lián)的一些屬性和方法。

AMD是依賴關(guān)系前置,在定義模塊的時(shí)候就要聲明其依賴的模塊;
CMD是按需加載依賴就近,只有在用到某個(gè)模塊的時(shí)候再去require:

// CMD
define(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  // 此處略去 100 行
  var b = require('./b') // 依賴可以就近書寫
  b.doSomething()
  // ... 
})

// AMD 默認(rèn)推薦的是
define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好
  a.doSomething()
  // 此處略去 100 行
  b.doSomething()
  ...
}) 
seajs使用例子
// 定義模塊  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
  exports.data = 1;
});

// 加載模塊
seajs.use(['myModule.js'], function(my){
    var star= my.data;
    console.log(star);  //1
});

參考

這篇《前端模塊化:CommonJs,AMD和CDM》主要是個(gè)人對(duì)以下文章的總結(jié)腰素,感謝這些老司機(jī)們的分享聘裁。
前端模塊化
詳解JavaScript模塊化開發(fā)
Javascript模塊化編程
從 CommonJS 到 Sea.js

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市弓千,隨后出現(xiàn)的幾起案子衡便,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镣陕,死亡現(xiàn)場離奇詭異谴餐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)呆抑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門岂嗓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鹊碍,你說我怎么就攤上這事厌殉。” “怎么了侈咕?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵公罕,是天一觀的道長。 經(jīng)常有香客問我乎完,道長熏兄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任树姨,我火速辦了婚禮摩桶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帽揪。我一直安慰自己硝清,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布转晰。 她就那樣靜靜地躺著芦拿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪查邢。 梳的紋絲不亂的頭發(fā)上蔗崎,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音扰藕,去河邊找鬼缓苛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛邓深,可吹牛的內(nèi)容都是我干的未桥。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼芥备,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼冬耿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起萌壳,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤亦镶,失蹤者是張志新(化名)和其女友劉穎日月,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體染乌,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡山孔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荷憋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片台颠。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖勒庄,靈堂內(nèi)的尸體忽然破棺而出串前,到底是詐尸還是另有隱情,我是刑警寧澤实蔽,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布荡碾,位于F島的核電站,受9級(jí)特大地震影響局装,放射性物質(zhì)發(fā)生泄漏坛吁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一铐尚、第九天 我趴在偏房一處隱蔽的房頂上張望拨脉。 院中可真熱鬧,春花似錦宣增、人聲如沸玫膀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帖旨。三九已至,卻和暖如春灵妨,著一層夾襖步出監(jiān)牢的瞬間解阅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工泌霍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留货抄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓烹吵,卻偏偏與公主長得像碉熄,于是被迫代替她去往敵國和親桨武。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肋拔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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