關于前端模塊化

1. 為什么要使用模塊化?

JS的發展初期 ,僅需實現簡單的用戶交互邏輯,而隨著CPU、瀏覽器性能的大幅度提升,很多頁面邏輯遷移到了客戶端,且隨著web2.0時代的到來,Ajax技術得到了廣泛的應用,jQuery等前端庫層出不窮,前端代碼日益膨脹。此時JS極其簡單的代碼組織已無法駕馭如此龐大規模的代碼,此時參考其他的語言,例如JAVA有一個package的概念,將邏輯上相關的代碼組織到一個包里,每個包互相獨立,不會相互影響,這樣就可以將代碼分塊組織。而JS在設計之初,并未提供類似的功能,因此開發者開始模擬類似的功能,來隔離、組織復雜的JS代碼,這就是前端模塊化的開始。
模塊化最初的思路就是在一個文件中編寫幾個相關的函數,需要的時候加載函數所在的文件,調用函數。然而這樣做污染了全局變量,無法保證不與其他模塊變量名沖突,且模塊成員之間沒有什么關系。
之后為解決上面的問題,采用對象的寫法,將所有的模塊成員封裝到一個對象中,var module = {fn1: function(){...}, fn2: function(){...}}在調用模塊的時候引用對應的文件module.fn(),這樣做避免了污染變量,同時模塊里的成員也有了關系,但是在外部可以隨意修改內部成員var module.fn1 = 1
最后出現了一種思路,就是通過立即執行函數,來隱藏內部的變量與函數,立即執行函數將內部的變量與函數都包裹在自己的作用域中,外部無法修改,這種做法就是現階段模塊化的基礎。
目前JS模塊化規范主要有兩種:CommonJS和AMD。

2.CMD、AMD、CommonJS 規范分別指什么?有哪些應用

CommonJS

CommonJS是在服務器端的規范,由Node.js發揚光大。CommonJS包含以下三部分:

  • 定義模塊:根據CommonJS規范,一個單獨的文件就是一個模塊。每一個模塊都是一個單獨的作用域,也即在該模塊內部定義的變量,無法被其他模塊讀取,除非定義為global對象的屬性。
  • 模塊輸出:模塊只有一個出口,modules.export對象,我們把模塊希望輸出的內容放入該對象。
  • 加載模塊:加載模塊使用require方法,該方法讀取一個文件并執行,返回文件內部的module.exports對象。
//定義一個模塊math.js
module.exports = function(){
  var sum = 0, i = 0, args = arguments
  while(i < args.length){
    sum += args[i++]
  }
  return sum;
};
//main.js加載模塊
var add = require('math.js')
console.log(add(1,2,3)) // 用node運行,輸出結果為6

上述代碼首先定義一個了模塊math.js,并通過module.exports輸出了一個函數,接著在另一個文件中用require加載了這個函數。這就是CommonJS的基本用法。然而可以注意到,上面的代碼中require是同步的,模塊系統需同步讀取模塊文件內容,并編譯執行以得到模塊接口。這在服務器端可以實現,但是在瀏覽器端,加載JS代碼最常見的方式是在document中插入script標簽,但script標簽天生是異步的,所以CommonJS規范無法在瀏覽器端實施。
所以就有一種思路,可以用一套標準模板來封裝模板定義,于是有了以下兩種規范:AMD和CMD。

AMD

AMD(Asynchronous Module Definition),異步模塊定義,是一個在瀏覽器端開發的模塊化規范。由于原生JS不支持AMD規范,因此要用到一個庫函數"require.js"。AMD采用異步加載的模式,模塊的加載不影響它后面語句的運行。所有依賴這個模塊的語句,都定義在一個回調函數中,等加載完成后,這個回調函數才會運行。依舊用上述的例子,首先在html里通過script標簽里引入require.js:
<script src="./require.js" data-main="./main.js"></script>
以上代碼中data-main屬性的作用是指定網頁程序的主模塊,即main.js會第一個被require.js加載。

//定義模塊math.js
define(function (){
    var add=function(){
        var sum=0,i=0,args = arguments;
        while(i < args.length){
            sum += args[i++];
        }
        return sum
    }
    return {
        add: add
    };
})
//main.js加載模塊
require(['math'],function(math){
    console.log(math.add(1,2,3))
}) // 在瀏覽器端運行,輸出結果為6。

requireJS定義了一個函數define,它是一個全局變量,用于定義模塊。語法為:
define([[id,] dependencies,] factory)

  • id 可選參數,用于定義模塊的標識,如果沒有就默認為腳本文件名(去掉擴展名)
  • dependencies 可選參數,是當前模塊依賴的模塊名稱數組
  • factory 工廠方法,模塊初始化要執行的函數或對象,如果是對象,該對象為該模塊的輸出值,如果是函數,則應該只執行一次。
    加載模塊使用require函數
    require([dependencies], function(){})
    require函數接收兩個參數,前一個為一個數組,表示依賴的模塊,后一個為一個回調函數,將在所有依賴加載成功后調用,加載的模塊會以參數的形式傳入該函數,從而在該函數內部可以使用這些模塊。由于require函數在加載依賴的時候是異步加載,這樣瀏覽器不會失去響應,并且會等所有依賴加載成功后再執行回調函數,解決了依賴性問題。

CMD

另一種規范叫做CMD(Common Module Definition),通用模板定義,是由國內發展而來。就如AMD有一個requireJS,CMD用的是seaJS。seaJS要解決的問題和requireJS一樣,只是在模塊定義方式和模塊加載時機上有所不同。
CMD規范中,一個模塊就是一個文件,寫法為:
define(factory)
factory為函數時,表示模塊的構造方法。執行該構造方法,可以得到模塊向外提供的接口。factory有三個參數:
function(require,exports,module)

  • require是一個方法,require(id)接收模塊標識作為唯一參數,用來獲取其他模塊提供的接口。
  • exports是一個對象,用來向外提供模塊接口。
  • module是一個對象,該對象上存儲了與當前模塊相關聯的一些屬性和方法。
//定義模塊module.js
define(function(require,exports,module){
  var a = require('./a')
  a.dosomething
  //some code
  var b = require('./b')
  b.dosomething
  //some code
}
//加載模塊
seajs.use(['module.js'],function(){
// do something
});

從上述代碼可以看出,CMD與AMD的最大的不同點在于CMD推崇依賴就近,即只有在用到某個模塊的時候再去require,而AMD推崇依賴前置,在定義模塊的時候就要聲明其依賴。
同樣是異步加載 ,AMD會在加載模塊完成后就執行該模塊,所有模塊加載執行完后進入require的回調函數,執行主邏輯。所以依賴模塊的執行順序和書寫順序不一定一致,哪個先下載,哪個先執行。而CMD加載完某個依賴模塊后并不執行,只是下載而已,在所有依賴模塊加載完成后,進入主邏輯,遇到require語句的時候才執行對應的模塊,所以模塊的執行順序和書寫順序是完全一致的。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容