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語句的時候才執行對應的模塊,所以模塊的執行順序和書寫順序是完全一致的。