JavaScript模塊化編程-require.js
隨著網(wǎng)頁(yè)越來(lái)越復(fù)雜、代碼量越來(lái)越巨大,網(wǎng)頁(yè)已經(jīng)趨近于桌面應(yīng)用,因此JavaScript模塊化編程顯得尤為重要。理想情況下,開(kāi)發(fā)者只需要關(guān)注核心業(yè)務(wù)邏輯的實(shí)現(xiàn),其他都可以加載別人已經(jīng)寫(xiě)好的模塊。
一、什么是模塊?
我們先來(lái)看一下通常情況下js的寫(xiě)法
var a = 'hello'
function foo(){
// do something
}
在這個(gè)例子中,我們暴露了一下全局變量,如a,foo等,并且代碼沒(méi)有很好的分離開(kāi)來(lái),這樣的代碼,可以稱(chēng)為“面條式代碼”,這些代碼看起來(lái)雜亂無(wú)章,我們希望的是這些代碼也可以跟html頁(yè)面中的div一樣,分割成一個(gè)一個(gè)的“豆腐塊”,例如
// play.js
function foo1(){}
function foo2(){}
// tab.js
function tab(){}
函數(shù)foo1和函數(shù)foo2分割成了兩個(gè)獨(dú)立的部分,它們負(fù)責(zé)不同的功能,相互之間互不影響,那么,我們可以說(shuō)foo1和foo2是兩個(gè)不同的模塊。當(dāng)foo1和foo2在play.js這個(gè)文件里,例如表示該文件是某個(gè)播放功能的實(shí)現(xiàn),而tab.js文件表示某個(gè)切換功能的實(shí)現(xiàn),這兩個(gè)文件表示不同功能,它們之間相互獨(dú)立,互不影響,也可以認(rèn)為是兩個(gè)獨(dú)立的模塊。
因此我們可以認(rèn)為一個(gè)模塊就是一個(gè)獨(dú)立的功能,它可以是一個(gè)函數(shù),也可以是一個(gè)對(duì)象,還可以是一個(gè)文件。
二、產(chǎn)生的問(wèn)題
但是這又出現(xiàn)了另外一個(gè)問(wèn)題,暴露了foo1和foo2兩個(gè)全局變量,無(wú)法保證不與其他模塊產(chǎn)生沖突。為了解決這個(gè)問(wèn)題,我們可以通過(guò)立即執(zhí)行函數(shù)來(lái)處理
!function(){
foo1(){}
foo2(){}
}()
我們執(zhí)行立即執(zhí)行函數(shù)可以避免產(chǎn)生全局變量的問(wèn)題,這樣foo1()和foo2()就都是局部變量了。
不過(guò)又出現(xiàn)了另外一個(gè)問(wèn)題,假如模塊與模塊之間有依賴(lài)關(guān)系怎么辦呢?一個(gè)簡(jiǎn)單的例子就是當(dāng)我們引入jQuery時(shí)進(jìn)行編程,jQuery就可以認(rèn)為是一個(gè)依賴(lài),我們可以通過(guò)傳入這個(gè)依賴(lài)來(lái)解決
!function($){
foo1(){}
foo2(){}
}(jQuery)
但是當(dāng)模塊與模塊之間有較多的依賴(lài),一個(gè)模塊必須要等到所依賴(lài)的模塊加載完成,才可以進(jìn)行后面的代碼,而且這個(gè)模塊必須要寫(xiě)到所依賴(lài)的模塊之后,上面的例子中這部分的代碼必須寫(xiě)到引入的jQuery之后,否則就會(huì)出現(xiàn)錯(cuò)誤。因此,瀏覽器端的模塊,不能采用”同步加載”(synchronous),只能采用”異步加載”(asynchronous)。
三、AMD
AMD是”Asynchronous Module Definition”的縮寫(xiě),意思就是”異步模塊定義”。它采用異步方式加載模塊,模塊的加載不影響它后面語(yǔ)句的運(yùn)行。所有依賴(lài)這個(gè)模塊的語(yǔ)句,都定義在一個(gè)回調(diào)函數(shù)中,等到加載完成之后,這個(gè)回調(diào)函數(shù)才會(huì)運(yùn)行。
require([module], callback);
第一個(gè)參數(shù)[module],是一個(gè)數(shù)組,里面的成員就是要加載的模塊;第二個(gè)參數(shù)callback,則是加載成功之后的回調(diào)函數(shù)。我們要介紹的require.js就是AMD規(guī)范實(shí)現(xiàn)的一個(gè)庫(kù)。
四、require.js
根據(jù)之前描述,當(dāng)多個(gè)模塊產(chǎn)生依賴(lài),首先,加載的時(shí)候,瀏覽器會(huì)停止網(wǎng)頁(yè)渲染,加載文件越多,網(wǎng)頁(yè)失去響應(yīng)的時(shí)間就會(huì)越長(zhǎng);其次,由于js文件之間存在依賴(lài)關(guān)系,因此必須嚴(yán)格保證加載順序(比如上例的jQuery文件要在foo文件的前面),依賴(lài)性最大的模塊一定要放到最后加載,當(dāng)依賴(lài)關(guān)系很復(fù)雜的時(shí)候,代碼的編寫(xiě)和維護(hù)都會(huì)變得困難。
require.js可以很好的解決這兩個(gè)問(wèn)題
- 實(shí)現(xiàn)js文件的異步加載,避免網(wǎng)頁(yè)失去響應(yīng);
- 管理模塊之間的依賴(lài)性,便于代碼的編寫(xiě)和維護(hù)。
使用require.js
首先新建一個(gè)requirejs-demo的目錄,安裝require.js
也可以去官網(wǎng)下載
安裝完成之后把它移動(dòng)到scripts子目錄下,
在index.html中引入
<script data-main="scripts/main" src="scripts/require.js"></script>
data-main屬性的作用是,指定網(wǎng)頁(yè)程序的主模塊。在上例中,就是scripts目錄下面的main.js,這個(gè)文件會(huì)第一個(gè)被require.js加載。由于require.js默認(rèn)的文件后綴名是js,所以可以把main.js簡(jiǎn)寫(xiě)成main。
假設(shè)現(xiàn)在 main.js 依賴(lài) hello.js,hello.js 依賴(lài) nany.js,最終需要在頁(yè)面上輸出’hello,nany’,那么我們可以這樣實(shí)現(xiàn)
// main.js
require(['./hello'],function(x){
document.write(x.x.name)
})
// 表示main.js主模塊依賴(lài)hello.js當(dāng)hello.js加載完成之后,再執(zhí)行main.js指定的回調(diào)函數(shù)
// hello.js
define(['./nany'],function(x){
return {
x
}
});
// 在hello.js中,它依賴(lài)nany.js這個(gè)模塊,加載nany.js完畢后,將nany.js保存的對(duì)象返回給hello.js
require.js加載的模塊,采用AMD規(guī)范。也就是說(shuō),模塊必須按照AMD的規(guī)定來(lái)寫(xiě)。具體來(lái)說(shuō),就是模塊必須采用特定的define()函數(shù)來(lái)定義。如果一個(gè)模塊不依賴(lài)其他模塊,那么可以直接定義在define()函數(shù)之中。如果這個(gè)模塊還依賴(lài)其他模塊,那么define()函數(shù)的第一個(gè)參數(shù),必須是一個(gè)數(shù)組,指明該模塊的依賴(lài)性。第二個(gè)參數(shù)為回調(diào)函數(shù)。
// nany.js
define({
name: 'hello, nany'
})
當(dāng)我們打開(kāi)index.html,可以看到

