導(dǎo)語(yǔ):
之前一直有聽說RequireJS,但是一直都沒機(jī)會(huì)去了解貌矿,只知道它是一個(gè)給js做模塊化的API炭菌。最近在做React,其組件化的思想和js模塊化的思想不謀而合逛漫。就想在項(xiàng)目中應(yīng)用React的同時(shí)黑低,也把RequireJS加進(jìn)來,看看會(huì)不會(huì)對(duì)頁(yè)面加載或者開發(fā)有很好的效果尽楔。
What is RequireJS投储?
在說明什么是RequireJS之前,不得不提的就是Javascript模塊化歷史的背景阔馋。其實(shí)在早期玛荞,javascript作為一門新興的腳本語(yǔ)言出現(xiàn),有著龐大的愿景呕寝,它并不是作為一門僅僅針對(duì)客戶端設(shè)計(jì)的語(yǔ)言勋眯。只是說后來web應(yīng)用的流行,javascript作為瀏覽器端腳本語(yǔ)言而迅速傳開下梢,加上Netscape和微軟的競(jìng)爭(zhēng)將其過早的標(biāo)準(zhǔn)化客蹋。所以就導(dǎo)致了JS的諸多缺陷,其中一個(gè)就是模塊化(但是你可以驚奇地發(fā)現(xiàn)其實(shí)javascript有將import,export等作為保留字孽江,說明設(shè)計(jì)的時(shí)候其實(shí)是有考慮的讶坯,新的標(biāo)準(zhǔn)es6也讓原生支持模塊化了)。然后隨著web應(yīng)用越來越復(fù)雜岗屏,嵌入的javascript代碼越來越多辆琅,還有node的興起,模塊化編程就變成了必須这刷。
所以就有了后來Dojo工具包和Google的Closure庫(kù)支持的模塊系統(tǒng)婉烟。還有兩個(gè)非常通用的標(biāo)準(zhǔn)規(guī)范,CommonJS和AMD暇屋。這里就不展開說了似袁,我們只需要知道,實(shí)現(xiàn)CommonJS規(guī)范的API是同步加載模塊的咐刨,而實(shí)現(xiàn)AMD規(guī)范的API是則是異步加載模塊昙衅。
所以理論上來說,AMD規(guī)范的非阻塞加載更加適合瀏覽器端定鸟。而RequireJS就是AMD規(guī)范的最好實(shí)現(xiàn)绒尊。抄一段官方文檔對(duì)RequireJS的描述:
RequireJS 是一個(gè)JavaScript模塊加載器。它非常適合在瀏覽器中使用, 它非常適合在瀏覽器中使用仔粥,但它也可以用在其他腳本環(huán)境, 就像 Rhino and Node. 使用RequireJS加載模塊化腳本將提高代碼的加載速度和質(zhì)量。
Why RequireJS?
所以躯泰,知道了RequireJS是干什么的谭羔,也差不多知道為什么我們要使用RequireJS了。不過還是總結(jié)一下用RequireJS的好處吧麦向。
- 異步“加載”瘟裸。我們知道,通常網(wǎng)站都會(huì)把script腳本的放在html的最后诵竭,這樣就可以避免瀏覽器執(zhí)行js帶來的頁(yè)面阻塞话告。使用RequireJS,會(huì)在相關(guān)的js加載后執(zhí)行回調(diào)函數(shù)卵慰,這個(gè)過程是異步的沙郭,所以它不會(huì)阻塞頁(yè)面。
- 按需加載裳朋。通過RequireJS病线,你可以在需要加載js邏輯的時(shí)候再加載對(duì)應(yīng) 的js模塊,這樣避免了在初始化網(wǎng)頁(yè)的時(shí)候發(fā)生大量的請(qǐng)求和數(shù)據(jù)傳輸鲤嫡,或許對(duì)于一些人來說送挑,某些模塊可能他根本就不需要,那就顯得沒有必要暖眼。
- 更加方便的模塊依賴管理惕耕。相信你曾經(jīng)一定遇到過因?yàn)閟cript標(biāo)簽順序問題而導(dǎo)致依賴關(guān)系發(fā)生錯(cuò)誤,這個(gè)函數(shù)未定義诫肠,那個(gè)變量undefine之類的司澎。通過RequireJS的機(jī)制,你能確保在所有的依賴模塊都加載以后再執(zhí)行相關(guān)的文件区赵,所以可以起到依賴管理的作用惭缰。
- 更加高效的版本管理。想一想笼才,如果你還是用的script腳本引入的方式來引入一個(gè)jQuery2.x的文件漱受,然后你有100個(gè)頁(yè)面都是這么引用的,那當(dāng)你想換成jQuery3.x骡送,那你就不得不去改這100個(gè)頁(yè)面昂羡。但是如果你的requireJS有在config中做jQuery的path映射,那你只需要改一處地方即可摔踱。
- 當(dāng)然還有一些諸如cdn加載不到j(luò)s文件虐先,可以請(qǐng)求本地文件等其它的優(yōu)點(diǎn),這里就不一一列舉了派敷。
RequireJS 使用
需要在頁(yè)面中引入的文件
<script data-main="js/main" src="xxx/xxxx/require.js"></script>
使用RequireJS蛹批,你只需要引入一個(gè)require.js即可撰洗。需要說明的是,一個(gè)比較好的實(shí)踐腐芍,就是你的頁(yè)面上面應(yīng)該也只需要通過<script>標(biāo)簽引入這一個(gè)js即可差导。然后你這個(gè)頁(yè)面的所有業(yè)務(wù)邏輯只需要在main.js里面寫(data-main屬性作用后面會(huì)有講)就可以了。其它引用的依賴怎么辦猪勇?當(dāng)然是通過require按需引入吧韬帧!
Require基本概述
其實(shí)Requirejs整個(gè)源文件包括注釋就2000來行泣刹,其對(duì)外暴露的變量其實(shí)就三個(gè),requirejs,require,define助析。
這其中requirejs 只是require的一個(gè)別名,目的是如果頁(yè)面中有require其它實(shí)現(xiàn)了椅您,你還是能通過使用requirejs來使用requireJS API的(本文中沒有相關(guān)沖突外冀,所以還是使用require)。
所以這意味著作為入門襟沮,你只需要掌握require,require.config,define這三樣就可以了锥惋。
本文將以介紹require,require.config,data-main,define的順序來介紹RequireJS。讓比較簡(jiǎn)單的RequireJS更加簡(jiǎn)單开伏,爭(zhēng)取讓大家只看這篇文章就能用好RequireJS膀跌。至于RequireJS是如何解決循環(huán)依賴,對(duì)于沒有實(shí)現(xiàn)amd的模塊如何通過shim來導(dǎo)出,如何在node中使用等問題固灵。本文并沒有提及捅伤,詳細(xì)有需要可以去官方查閱。
require
首先巫玻,先不管三七二十一丛忆,我們先按照下面的方式創(chuàng)建一個(gè)這樣的目錄結(jié)構(gòu):
然后require.js可以通過npm下載或者在官網(wǎng)獲得。jquery同理仍秤,jquery需要下載1.7.0或以上的版本熄诡。然后把對(duì)應(yīng)的代碼拷入對(duì)應(yīng)的文件中,給出余下兩個(gè)文件的代碼:
// js/script/index.html
<!DOCTYPE html>
<html>
<head>
<title>Require Demo 1</title>
</head>
<body>
<div>
<h1>Require Demo 1 -- usage of Require()</h1>
<button id="contentBtn">Click me</button>
<p id="messagebox"></p>
</div>
<script data-main="js/script/main" src="js/lib/require.js" type="text/javascript"></script>
</body>
</html>
// js/script/main.js
require.config(
{
paths: {
'jquery': '../lib/jquery-1.7.2'
}
}
);
require(['jquery'],function ($) {
$(document).on('click','#contentBtn',function(){
$('#messagebox').html('You have access Jquery by using require()');
});
});
先看index.html的代碼诗力,其實(shí)比較簡(jiǎn)單凰浮,頁(yè)面上在js中會(huì)用到的就是一個(gè)button和一個(gè)p標(biāo)簽。然后整個(gè)頁(yè)面就只是一個(gè)js文件是通過<script>標(biāo)簽加載的苇本,就是require.js袜茧。注意到標(biāo)簽中有一個(gè)data-main屬性,你現(xiàn)在只需要了解require.js會(huì)在加載完成以后通過回調(diào)方法去加載這個(gè)data-main里面的js文件瓣窄,所以這個(gè)js文件被加載的時(shí)候笛厦,RequireJS已經(jīng)加載執(zhí)行完畢。
然后接著看main.js文件,里面被一個(gè)匿名立即執(zhí)行函數(shù)所包括俺夕。在require.config(...)中裳凸,可以配置許多配置項(xiàng)贱鄙,后面會(huì)有詳細(xì)說明。上面在config中添加了一個(gè)path姨谷,在path配置了一個(gè)模塊ID和路徑的映射贰逾,這樣在后續(xù)的所有函數(shù)中就可以直接通過模塊ID來引入依賴,而不用再多次引入依賴多次輸入路徑帶來的麻煩菠秒。
然后接著就是我們的require(...)函數(shù)了。上面的語(yǔ)法中require函數(shù)接受的第一個(gè)參數(shù)是氯迂,所依賴模塊的一個(gè)數(shù)組践叠。即使你只需要傳入一個(gè)依賴,也需要把這個(gè)依賴放進(jìn)數(shù)組中傳入嚼蚀。如果你有如本例子中設(shè)置了模塊ID和路徑的映射禁灼,那你在傳入依賴的時(shí)候就可以使用模塊ID來代替路徑,如果沒有配置模塊ID你當(dāng)然也可以通過路徑來引進(jìn)對(duì)應(yīng)的模塊轿曙。接著是傳入回調(diào)函數(shù)弄捕,當(dāng)引入的依賴加載完畢后,這個(gè)回調(diào)函數(shù)就會(huì)被觸發(fā)导帝。如果你傳入的依賴有注入變量(函數(shù)),然后在回調(diào)函數(shù)中需要用到守谓,你就需要按照順序在回調(diào)函數(shù)的參數(shù)中添加別名,在本例子中可以通過別名$來使用jQuery的相關(guān)API您单。所以有注入的模塊需要放在無注入或者不需要調(diào)用模塊的模塊前面斋荞,方便回調(diào)函數(shù)傳入別名。例子中在回調(diào)函數(shù)中為id為contentBtn的button注冊(cè)監(jiān)聽事件虐秦,如果觸發(fā)平酿,則往id為messagebox的p標(biāo)簽添加相應(yīng)的內(nèi)容。
另外還需要額外說明的是路徑悦陋,不管是在配置中寫路徑還是直接在require函數(shù)中寫路徑蜈彼,你都需要了解requireJS在不同情況下的相對(duì)路徑。
以下是相對(duì)路徑的規(guī)則:
1.如果<script>標(biāo)簽引入require.js時(shí)沒有指定data-main屬性俺驶,則以引入該js的html文件所在的路徑為根路徑幸逆。
2.如果有指定data-main屬性,也就是有指定入口文件痒钝,則以入口文件所在的路徑為根路徑秉颗。在本例子中也就是main.js所在的script文件夾就是根路徑,這也是為什么配置jQuery的時(shí)候需要返回上層目錄再進(jìn)入lib目錄才能找到j(luò)Query文件送矩。
3.如果再require.config()中有配置baseUrl蚕甥,則以baseUrl的路徑為根路徑。
以上三條優(yōu)先級(jí)逐級(jí)提升栋荸,如果有重疊菇怀,后面的根路徑覆蓋前面的根路徑凭舶。
打開網(wǎng)頁(yè),然后你就應(yīng)該看到這樣的頁(yè)面:
點(diǎn)擊按鈕爱沟,有如下效果帅霜,說明通過RequireJS已載入Jquery,并且通過Jquery綁定了監(jiān)聽事件呼伸。
define
講完了如何引入模塊身冀,現(xiàn)在講如何定義一個(gè)模塊,require定義一個(gè)模塊是通過 define = function (name, deps, callback)完成的,第一個(gè)參數(shù)是定義模塊名,第二個(gè)參數(shù)是傳入定義模塊所需要的依賴括享,第三個(gè)函數(shù)則是定義模塊的主函數(shù)搂根,主函數(shù)和require的回調(diào)函數(shù)一樣,同樣是在依賴加載完以后再調(diào)用執(zhí)行铃辖。
先看個(gè)例子:
當(dāng)你沒有任何依賴的時(shí)候,你可以這么寫:
// js/script/desc.js
define(function(){
return{
decs : 'this js will be request only if it is needed',
};
})
// 然后在main.js的添加如下代碼
// js/script/main.js
$('#messagebox').html('You have access Jquery by using require()');
+ require(['script/desc'],function(desc){
+ alert(JSON.stringify(desc));
再次打開網(wǎng)頁(yè)剩愧,打開network視圖,點(diǎn)擊按鈕娇斩,通過require獲得的desc模塊就會(huì)alert出來仁卷,同時(shí)你會(huì)發(fā)現(xiàn),desc.js是按需請(qǐng)求的犬第,并不是在頁(yè)面一開始的時(shí)候就請(qǐng)求的锦积。
當(dāng)你有相關(guān)依賴的時(shí)候,你可以這么寫:
// js/script/alertdesc.js
define(['script/desc'],function(desc){
return function (){
alert(JSON.stringify(desc));
};
})
// 然后在main.js的再做如下修改
// js/script/main.js
$('#messagebox').html('You have access Jquery by using require()');
- require(['script/desc'],function(desc){
- alert(JSON.stringify(desc));
+ require(['script/alertdesc'],function(alertdesc){
+ alertdesc();
為什么我始終都沒有使用name來定義自己的模塊名:
如果你細(xì)心,你可能會(huì)發(fā)現(xiàn)瓶殃,剛剛define函數(shù)充包,有一個(gè)參數(shù)name是用來定義模塊名的(也就是第一個(gè)傳參),為什么上面兩個(gè)例子都沒有用到遥椿。其實(shí)我確實(shí)可以添加模塊名基矮,如下:
// js/script/alertdesc.js
define(['script/desc'],function(desc){
.....
})
//Change To
define('/script/alertdesc',['script/desc'],function(desc){
.....
})
但是,這樣做感覺不很有必要冠场,因?yàn)槿绻囊惶煳覍⑦@個(gè)文件轉(zhuǎn)移到其他目錄下家浇,那我就得在這這里再修改一次模塊名。官方其實(shí)也不推薦碴裙,用官方的說法是:讓優(yōu)化工具去自動(dòng)生成這些模塊名吧钢悲!
require.config
在上面一節(jié)介紹require()函數(shù)的時(shí)候,我們已經(jīng)接觸過require.config(...)了舔株。其實(shí)說白了莺琳,在require.config()做的一些修改會(huì)影響到全局require的一些特性。如上面的载慈,你設(shè)置了baseUrl
惭等,其require的根路徑就以這個(gè)路徑為準(zhǔn),你在path中設(shè)置了模塊ID與路徑的映射办铡,后面需要用到相關(guān)模塊的時(shí)候直接使用模塊ID代替路徑就好了辞做,設(shè)置map可以在不同路徑下用相同的模塊ID調(diào)用不同版本的模塊琳要。
其實(shí)這里并不打算對(duì)require.config()的具體配置展開來介紹,有需要可以直接去官網(wǎng)查閱相關(guān)配置信息加進(jìn)來就好了秤茅。
始終覺得require.config()應(yīng)該抽出來稚补,單獨(dú)放在一個(gè)js文件里面,這樣方便移植和重用框喳。在github上看了些例子课幕,找到一個(gè)比較好的放置require.config的地方,放在這里可以參考:
// 添加config.js
// js/script/config.js
define(function(){
require.config({
baseUrl: './js/',
paths: {
'jquery': 'lib/jquery-1.7.2'
}
});
});
// 替換main.js
// js/script/main.js
require(['config'],function(){
require(['jquery'],function ($) {
$(document).on('click','#contentBtn',function(){
$('#messagebox').html('You have access Jquery by using require()');
require(['script/alertdesc'],function(alertdesc){
alertdesc();
});
});
});
});
data-main
還記得剛剛的<script>引入RequireJS時(shí)標(biāo)簽中有一個(gè)data-main屬性么五垮?當(dāng)require.js加載的時(shí)候會(huì)檢查data-main屬性撰豺,所以你可以在data-main指向的腳本(也就是本例子中的js/main.js)中設(shè)置模塊加載的選項(xiàng),然后在這個(gè)腳本加載第一個(gè)應(yīng)用模塊。注意拼余,你在main.js中所設(shè)置的腳本是異步加載并通過回調(diào)來執(zhí)行的,這意味著如果你在頁(yè)面中有通過<script>引入其它的腳本亩歹,那不能保證在main.js里面做的配置會(huì)在其它腳本中生效匙监。
例如:
<script data-main="scripts/main" src="scripts/require.js"></script>
<script src="scripts/other.js"></script>
// scripts/main.js:
require.config({
paths: {
foo: 'libs/foo-1.1.3'
}
});
// scripts/other.js:
// 由于main.js會(huì)是在require.js異步加載完以后再通過回調(diào)去執(zhí)行main.js的
// 所以other.js里面執(zhí)行的這個(gè)require函數(shù)可能會(huì)發(fā)生在main.js的require.config執(zhí)行之前
// 因此require.config會(huì)去嘗試去加載"scripts/foo.js",而不是"scripts/libs/foo-1.1.3.js"
require( ['foo'], function( foo ) {
});
這個(gè)例子是官方的小作。從這里也可以看出來亭姥,為什么如前文所說的。頁(yè)面中最好只有一個(gè)入口點(diǎn)文件(屬性data-main中引入的main.js)顾稀,然后這個(gè)入口點(diǎn)文件里引入或者編寫配置达罗,加載相關(guān)應(yīng)用模塊。
當(dāng)然你也可以像官方給的第二種方案静秆,不設(shè)置入口點(diǎn)粮揉,然后在每個(gè)require回調(diào)中再引入相關(guān)配置,不過那樣很麻煩而且不易于維護(hù)抚笔。這里就不給出例子了扶认,有需要可以去官網(wǎng)看。
總結(jié)
以上就是關(guān)于關(guān)于RequireJS簡(jiǎn)單使用的介紹了殊橙,大家有需要可以直接看源碼辐宾,大概就2000多行,不看具體實(shí)現(xiàn)膨蛮,看它對(duì)幾個(gè)函數(shù)聲明的描述叠纹,對(duì)使用起來也是很有幫助的,你會(huì)發(fā)現(xiàn)有一些連官方文檔都沒提及到的一些特性(比如require()方法可以直接傳入config配置作為第一個(gè)參數(shù))敞葛。
另外誉察,說一點(diǎn)小插曲,如果需要查閱RequireJS官方的API制肮,有條件的還是建議直接訪問英文官方文檔冒窍。如果說中文的官方文檔說還停留在老版本递沪,翻譯得比較生澀難懂就算了。一些很明顯有錯(cuò)誤的描述就真的是責(zé)任問題了综液。我在看中文文檔的時(shí)候真是各種難移理解款慨,后來直接看英文文檔,則順暢很多谬莹。不多說檩奠,貼張圖讓大家感受一下英文文檔和中文文檔對(duì)于waitSeconds的描述: