一篇文章掌握RequireJS常用知識(shí)
本文采取循序漸進(jìn)的方式,從理論到實(shí)踐,從RequireJS官方API文檔中,總結(jié)出在使用RequireJS過(guò)程中最常用的一些用法,并對(duì)文檔中不夠清晰具體的內(nèi)容,加以例證和分析,分享給大家供大家參考,具體內(nèi)容如下
1. 模塊化
相信每個(gè)前端開(kāi)發(fā)人員在剛開(kāi)始接觸js編程時(shí),都寫(xiě)過(guò)類(lèi)似下面這樣風(fēng)格的代碼:
<script type="text/javascript"> var a = 1; var b = 2; var c = a * a + b * b; if(c> 1) { alert('c > 1'); } function add(a, b) { return a + b; } c = add(a,b); </script>
<a href="javascript:;" onclick="click(this);" title="">請(qǐng)點(diǎn)擊</a>
這些代碼的特點(diǎn)是:
- 到處可見(jiàn)的全局變量
- 大量的函數(shù)
- 內(nèi)嵌在html元素上的各種js調(diào)用
當(dāng)然這些代碼本身在實(shí)現(xiàn)功能上并沒(méi)有錯(cuò)誤,但是從代碼的可重用性,健壯性以及可維護(hù)性來(lái)說(shuō),這種編程方式是有問(wèn)題的,尤其是在頁(yè)面邏輯較為復(fù)雜的應(yīng)用中,這些問(wèn)題會(huì)暴露地特別明顯:
- 全局變量極易造成命名沖突
- 函數(shù)式編程非常不利于代碼的組織和管理
- 內(nèi)嵌的js調(diào)用很不利于代碼的維護(hù),因?yàn)閔tml代碼有的時(shí)候是十分臃腫和龐大的
所以當(dāng)這些問(wèn)題出現(xiàn)的時(shí)候,js大牛們就開(kāi)始尋找去解決這些問(wèn)題的究極辦法,于是模塊化開(kāi)發(fā)就出現(xiàn)了。正如模塊化這個(gè)概念的表面意思一樣,它要求在編寫(xiě)代碼的時(shí)候,按層次,按功能,將獨(dú)立的邏輯,封裝成可重用的模塊,對(duì)外提供直接明了的調(diào)用接口,內(nèi)部實(shí)現(xiàn)細(xì)節(jié)完全私有,并且模塊之間的內(nèi)部實(shí)現(xiàn)在執(zhí)行期間互不干擾,最終的結(jié)果就是可以解決前面舉例提到的問(wèn)題。一個(gè)簡(jiǎn)單遵循模塊化開(kāi)發(fā)要求編寫(xiě)的例子:
//module.js var student = function (name) { return name && { getName: function () { return name; } }; }, course = function (name) { return name && { getName: function () { return name; } } }, controller = function () { var data = {}; return { add: function (stu, cour) { var stuName = stu && stu.getName(), courName = cour && cour.getName(), current, _filter = function (e) { return e === courName; }; if (!stuName || !courName) return; current = data[stuName] = data[stuName] || []; if (current.filter(_filter).length === 0) { current.push(courName); } }, list: function (stu) { var stuName = stu && stu.getName(), current = data[stuName]; current && console.log(current.join(';')); } } }; //main.js var stu = new student('lyzg'), c = new controller(); c.add(stu,new course('javascript')); c.add(stu,new course('html')); c.add(stu,new course('css')); c.list(stu);
以上代碼定義了三個(gè)模塊分別表示學(xué)生,課程和控制器,然后在main.js中調(diào)用了controller提供的add和list接口,為lyzg這個(gè)學(xué)生添加了三門(mén)課程,然后在控制臺(tái)顯示了出來(lái)。運(yùn)行結(jié)果如下:
javascript;html;css
通過(guò)上例,可以看出模塊化的代碼結(jié)構(gòu)和邏輯十分清晰,代碼看起來(lái)十分優(yōu)雅,另外由于邏輯都通過(guò)模塊拆分,所以達(dá)到了解耦的目的,代碼的功能也會(huì)比較健壯。不過(guò)上例使用的這種模塊化開(kāi)發(fā)方式也并不是沒(méi)有問(wèn)題,這個(gè)問(wèn)題就是它還是把模塊引用如student這些直接添加到了全局空間下,雖然通過(guò)模塊減少了很多全局空間的變量和函數(shù),但是模塊引用本身還是要依賴(lài)全局空間,才能被調(diào)用,當(dāng)模塊較多,或者有引入第三方模塊庫(kù)時(shí),仍然可能造成命名沖突的問(wèn)題,所以這種全局空間下的模塊化開(kāi)發(fā)的方式并不是最完美的方式。目前常見(jiàn)的模塊化開(kāi)發(fā)方式,全局空間方式是最基本的一種,另外常見(jiàn)的還有遵循AMD規(guī)范的開(kāi)發(fā)方式,遵循CMD規(guī)范的開(kāi)發(fā)方式,和ECMAScript 6的開(kāi)發(fā)方式。需要說(shuō)明的是,CMD和ES6跟本文的核心沒(méi)有關(guān)系,所以不會(huì)在此介紹,后面的內(nèi)容主要介紹AMD以及實(shí)現(xiàn)了AMD規(guī)范的RequireJS。
2. AMD規(guī)范
正如上文提到,實(shí)現(xiàn)模塊化開(kāi)發(fā)的方式,另外常見(jiàn)的一種就是遵循AMD規(guī)范的實(shí)現(xiàn)方式,不過(guò)AMD規(guī)范并不是具體的實(shí)現(xiàn)方式,而僅僅是模塊化開(kāi)發(fā)的一種解決方案,你可以把它理解成模塊化開(kāi)發(fā)的一些接口聲明,如果你要實(shí)現(xiàn)一個(gè)遵循該規(guī)范的模塊化開(kāi)發(fā)工具,就必須實(shí)現(xiàn)它預(yù)先定義的API。比如它要求在加載模塊時(shí),必須使用如下的API調(diào)用方式:
require([module], callback) 其中: [module]:是一個(gè)數(shù)組,里面的成員就是要加載的模塊; callback:是模塊加載完成之后的回調(diào)函數(shù)
所有遵循AMD規(guī)范的模塊化工具,都必須按照它的要求去實(shí)現(xiàn),比如RequireJS這個(gè)庫(kù),就是完全遵循AMD規(guī)范實(shí)現(xiàn)的,所以在利用RequireJS加載或者調(diào)用模塊時(shí),如果你事先知道AMD規(guī)范的話,你就知道該怎么用RequireJS了。規(guī)范的好處在于,不同的實(shí)現(xiàn)卻有相同的調(diào)用方式,很容易切換不同的工具使用,至于具體用哪一個(gè)實(shí)現(xiàn),這就跟各個(gè)工具的各自的優(yōu)點(diǎn)跟項(xiàng)目的特點(diǎn)有關(guān)系,這些都是在項(xiàng)目開(kāi)始選型的時(shí)候需要確定的。目前RequireJS不是唯一實(shí)現(xiàn)了AMD規(guī)范的庫(kù),像Dojo這種更全面的js庫(kù)也都有AMD的實(shí)現(xiàn)。
最后對(duì)AMD全稱(chēng)做一個(gè)解釋?zhuān)g為:異步模塊定義。異步強(qiáng)調(diào)的是,在加載模塊以及模塊所依賴(lài)的其它模塊時(shí),都采用異步加載的方式,避免模塊加載阻塞了網(wǎng)頁(yè)的渲染進(jìn)度。相比傳統(tǒng)的異步加載,AMD工具的異步加載更加簡(jiǎn)便,而且還能實(shí)現(xiàn)按需加載,具體解釋在下一部分說(shuō)明。
3. JavaScript的異步加載和按需加載
html中的script標(biāo)簽在加載和執(zhí)行過(guò)程中會(huì)阻塞網(wǎng)頁(yè)的渲染,所以一般要求盡量將script標(biāo)簽放置在body元素的底部,以便加快頁(yè)面顯示的速度,還有一種方式就是通過(guò)異步加載的方式來(lái)加載js,這樣可以避免js文件對(duì)html渲染的阻塞。
第1種異步加載的方式是直接利用腳本生成script標(biāo)簽的方式:
(function() { var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = 'http://yourdomain.com/script.js'; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); })();
這段代碼,放置在script標(biāo)記內(nèi)部,然后該script標(biāo)記添加到body元素的底部即可。
第2種方式是借助script的屬性:defer和async,defer這個(gè)屬性在IE瀏覽器和早起的火狐瀏覽器中支持,async在支持html5的瀏覽器上都支持,只要有這兩個(gè)屬性,script就會(huì)以異步的方式來(lái)加載,所以script在html中的位置就不重要了:
<script defer async="true" type="text/javascript" src="app/foo.js"></script> <script defer async="true" type="text/javascript" src="app/bar.js"></script> <script defer async="true" type="text/javascript" src="app/main.js"></script>
這種方式下,所有異步j(luò)s在執(zhí)行的時(shí)候還是按順序執(zhí)行的,不然就會(huì)存在依賴(lài)問(wèn)題,比如如果上例中的main.js依賴(lài)foo.js和bar.js,但是main.js先執(zhí)行的話就會(huì)出錯(cuò)了。雖然從來(lái)理論上這種方式也算不錯(cuò)了,但是不夠好,因?yàn)樗闷饋?lái)很繁瑣,而且還有個(gè)問(wèn)題就是頁(yè)面需要添加多個(gè)script標(biāo)記以及沒(méi)有辦法完全做到按需加載。
JS的按需加載分兩個(gè)層次,第一個(gè)層次是只加載這個(gè)頁(yè)面可能被用到的JS,第二個(gè)層次是在只在用到某個(gè)JS的時(shí)候才去加載。傳統(tǒng)地方式很容易做到第一個(gè)層次,但是不容易做到第二個(gè)層次,雖然我們可以通過(guò)合并和壓縮工具,將某個(gè)頁(yè)面所有的JS都添加到一個(gè)文件中去,最大程度減少資源請(qǐng)求量,但是這個(gè)JS請(qǐng)求到客戶(hù)端以后,其中有很多內(nèi)容可能都用不上,要是有個(gè)工具能夠做到在需要的時(shí)候才去加載相關(guān)js就完美解決問(wèn)題了,比如RequireJS。
4. RequireJS常用用法總結(jié)
前文多次提及RequireJS,本部分將對(duì)它的常用用法詳細(xì)說(shuō)明,它的官方地址是:http://www.requirejs.cn/,你可以到該地址去下載最新版RequireJS文件。RequireJS作為目前使用最廣泛的AMD工具,它的主要優(yōu)點(diǎn)是:
- 完全支持模塊化開(kāi)發(fā)
- 能將非AMD規(guī)范的模塊引入到RequireJS中使用
- 異步加載JS
- 完全按需加載依賴(lài)模塊,模塊文件只需要壓縮混淆,不需要合并
- 錯(cuò)誤調(diào)試
- 插件支持
4.01 如何使用RequireJS
使用方式很簡(jiǎn)單,只要一個(gè)script標(biāo)記就可以在網(wǎng)頁(yè)中加載RequireJS:
<script defer async="true" src="/bower_components/requirejs/require.js"></script>
由于這里用到了defer和async這兩個(gè)異步加載的屬性,所以require.js是異步加載的,你把這個(gè)script標(biāo)記放置在任何地方都沒(méi)有問(wèn)題。
4.02 如何利用RequireJS加載并執(zhí)行當(dāng)前網(wǎng)頁(yè)的邏輯JS
4.01解決的僅僅是RequireJS的使用問(wèn)題,但它僅僅是一個(gè)JS庫(kù),是一個(gè)被當(dāng)前頁(yè)面的邏輯所利用的工具,真正實(shí)現(xiàn)網(wǎng)頁(yè)功能邏輯的是我們要利用RequireJS編寫(xiě)的主JS,這個(gè)主JS(假設(shè)這些代碼都放置在main.js文件中)又該如何利用RJ來(lái)加載執(zhí)行呢?方式如下:
<script data-main="scripts/main.js" defer async="true" src="/bower_components/requirejs/require.js"></script>
對(duì)比4.01,你會(huì)發(fā)現(xiàn)script標(biāo)記多了一個(gè)data-main,RJ用這個(gè)配置當(dāng)前頁(yè)面的主JS,你要把邏輯都寫(xiě)在這個(gè)main.js里面。當(dāng)RJ自身加載執(zhí)行后,就會(huì)再次異步加載main.js。這個(gè)main.js是當(dāng)前網(wǎng)頁(yè)所有邏輯的入口,理想情況下,整個(gè)網(wǎng)頁(yè)只需要這一個(gè)script標(biāo)記,利用RJ加載依賴(lài)的其它文件,如jquery等。
4.03 main.js怎么寫(xiě)
假設(shè)項(xiàng)目的目錄結(jié)構(gòu)為:
main.js是跟當(dāng)前頁(yè)面相關(guān)的主JS,app文件夾存放本項(xiàng)目自定義的模塊,lib存放第三方庫(kù)。
html中按4.02的方式配置RJ。main.js的代碼如下:
require(['lib/foo', 'app/bar', 'app/app'], function(foo, bar, app) { //use foo bar app do sth });
在這段JS中,我們利用RJ提供的require方法,加載了三個(gè)模塊,然后在這個(gè)三個(gè)模塊都加載成功之后執(zhí)行頁(yè)面邏輯。require方法有2個(gè)參數(shù),第一個(gè)參數(shù)是數(shù)組類(lèi)型的,實(shí)際使用時(shí),數(shù)組的每個(gè)元素都是一個(gè)模塊的module ID,第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù),這個(gè)函數(shù)在第一個(gè)參數(shù)定義的所有模塊都加載成功后回調(diào),形參的個(gè)數(shù)和順序分別與第一個(gè)參數(shù)定義的模塊對(duì)應(yīng),比如第一個(gè)模塊時(shí)lib/foo,那么這個(gè)回調(diào)函數(shù)的第一個(gè)參數(shù)就是foo這個(gè)模塊的引用,在回調(diào)函數(shù)中我們使用這些形參來(lái)調(diào)用各個(gè)模塊的方法,由于回調(diào)是在各模塊加載之后才調(diào)用的,所以這些模塊引用肯定都是有效的。
從以上這個(gè)簡(jiǎn)短的代碼,你應(yīng)該已經(jīng)知道該如何使用RJ了。
4.04 RJ的baseUrl和module ID
在介紹RJ如何去解析依賴(lài)的那些模塊JS的路徑時(shí),必須先弄清楚baseUrl和module ID這兩個(gè)概念。
html中的base元素可以定義當(dāng)前頁(yè)面內(nèi)部任何http請(qǐng)求的url前綴部分,RJ的baseUrl跟這個(gè)base元素起的作用是類(lèi)似的,由于RJ總是動(dòng)態(tài)地請(qǐng)求依賴(lài)的JS文件,所以必然涉及到一個(gè)JS文件的路徑解析問(wèn)題,RJ默認(rèn)采用一種baseUrl + moduleID的解析方式,這個(gè)解析方式后續(xù)會(huì)舉例說(shuō)明。這個(gè)baseUrl非常重要,RJ對(duì)它的處理遵循如下規(guī)則:
- 在沒(méi)有使用data-main和config的情況下,baseUrl默認(rèn)為當(dāng)前頁(yè)面的目錄
- 在有data-main的情況下,main.js前面的部分就是baseUrl,比如上面的scripts/
- 在有config的情況下,baseUrl以config配置的為準(zhǔn)
上述三種方式,優(yōu)先級(jí)由低到高排列。
data-main的使用方式,你已經(jīng)知道了,config該如何配置,如下所示:
require.config({ baseUrl: 'scripts' });
這個(gè)配置必須放置在main.js的最前面。data-main與config配置同時(shí)存在的時(shí)候,以config為準(zhǔn),由于RJ的其它配置也是在這個(gè)位置配置的,所以4.03中的main.js可以改成如下結(jié)構(gòu),以便將來(lái)的擴(kuò)展:
require.config({ baseUrl: 'scripts' }); require(['lib/foo', 'app/bar', 'app/app'], function(foo, bar, app) { // use foo bar app do sth });
關(guān)于module ID,就是在require方法以及后續(xù)的define方法里,用在依賴(lài)數(shù)組這個(gè)參數(shù)里,用來(lái)標(biāo)識(shí)一個(gè)模塊的字符串。上面代碼中的['lib/foo', 'app/bar', 'app/app']就是一個(gè)依賴(lài)數(shù)組,其中的每個(gè)元素都是一個(gè)module ID。值得注意的是,module ID并不一定是該module 相關(guān)JS路徑的一部分,有的module ID很短,但可能路徑很長(zhǎng),這跟RJ的解析規(guī)則有關(guān)。下一節(jié)詳細(xì)介紹。
4.05 RJ的文件解析規(guī)則
RJ默認(rèn)按baseUrl + module ID的規(guī)則,解析文件,并且它默認(rèn)要加載的文件都是js,所以你的module ID里面可以不包含.js的后綴,這就是為啥你看到的module ID都是lib/foo, app/bar這種形式了。有三種module ID,不適用這種規(guī)則:
- 以/開(kāi)頭,如/lib/jquey.js
- 以.js結(jié)尾,如test.js
- 包含http或https,如http://xx.baidu.com/js/jquery.js
假如main.js如下使用:
require.config({ baseUrl: 'scripts' }); require(['/lib/foo', 'test.js', '/js/jquery'], function(foo, bar, app) { // use foo bar app do sth });
這三個(gè)module 都不會(huì)根據(jù)baseUrl + module ID的規(guī)則來(lái)解析,而是直接用module ID來(lái)解析,等效于下面的代碼:
<script src="/lib/foo.js"></script> <script src="test.js"></script> <script src="/js/jquery.js"></script>
各種module ID解析舉例:
例1,項(xiàng)目結(jié)構(gòu)如下:
main.js如下:
require.config({ baseUrl: 'scripts' }); require(['lib/foo', 'app/bar', 'app/app'], function(foo, bar, app) { // use foo bar app do sth });
baseUrl為:scripts目錄
moduleID為:lib/foo, app/bar, app/app
根據(jù)baseUrl + moduleID,以及自動(dòng)補(bǔ)后綴.js,最終這三個(gè)module的js文件路徑為:
scripts/lib/foo.js scripts/app/bar.js scripts/app/app.js
例2,項(xiàng)目結(jié)構(gòu)同例1:
main.js改為:
require.config({ baseUrl: 'scripts/lib', paths: { app: '../app' } }); require(['foo', 'app/bar', 'app/app'], function(foo, bar, app) { // use foo bar app do sth });
這里出現(xiàn)了一個(gè)新的配置paths,它的作用是針對(duì)module ID中特定的部分,進(jìn)行轉(zhuǎn)義,如以上代碼中對(duì)app這個(gè)部分,轉(zhuǎn)義為../app,這表示一個(gè)相對(duì)路徑,相對(duì)位置是baseUrl所指定的目錄,由項(xiàng)目結(jié)構(gòu)可知,../app其實(shí)對(duì)應(yīng)的是scirpt/app目錄。正因?yàn)橛羞@個(gè)轉(zhuǎn)義的存在,所以以上代碼中的app/bar才能被正確解析,否則還按baseUrl + moduleID的規(guī)則,app/bar不是應(yīng)該被解析成scripts/lib/app/bar.js嗎,但實(shí)際并非如此,app/bar被解析成scripts/app/bar.js,其中起關(guān)鍵作用的就是paths的配置。通過(guò)這個(gè)舉例,可以看出module ID并不一定是js文件路徑中的一部分,paths的配置對(duì)于路徑過(guò)程的js特別有效,因?yàn)榭梢院?jiǎn)化它的module ID。
另外第一個(gè)模塊的ID為foo,同時(shí)沒(méi)有paths的轉(zhuǎn)義,所以根據(jù)解析規(guī)則,它的文件路徑時(shí):scripts/lib/foo.js。
paths的配置中只有當(dāng)模塊位于baseUrl所指定的文件夾的同層目錄,或者更上層的目錄時(shí),才會(huì)用到../這種相對(duì)路徑。
例3,項(xiàng)目結(jié)果同例1,main.js同例2:
這里要說(shuō)明的問(wèn)題稍微特殊,不以main.js為例,而以app.js為例,且app依賴(lài)bar,當(dāng)然config還是需要在main.js中定義的,由于這個(gè)問(wèn)題在定義模塊的時(shí)候更加常見(jiàn),所以用define來(lái)舉例,假設(shè)app.js模塊如下定義:
define(['./bar'], function(bar) { return { doSth: function() { bar.doSth(); } } });
上面的代碼通過(guò)define定義了一個(gè)模塊,這個(gè)define函數(shù)后面介紹如何定義模塊的時(shí)候再來(lái)介紹,這里簡(jiǎn)單了解。這里這種用法的第一個(gè)參數(shù)跟require函數(shù)一樣,是一個(gè)依賴(lài)數(shù)組,第二個(gè)參數(shù)是一個(gè)回調(diào),也是在所有依賴(lài)加載成功之后調(diào)用,這個(gè)回調(diào)的返回值會(huì)成為這個(gè)模塊的引用被其它模塊所使用。
這里要說(shuō)的問(wèn)題還是跟解析規(guī)則相關(guān)的,如果完全遵守RJ的解析規(guī)則,這里的依賴(lài)應(yīng)該配置成app/bar才是正確的,但由于app.js與bar.js位于同一個(gè)目錄,所以完全可利用./這個(gè)同目錄的相對(duì)標(biāo)識(shí)符來(lái)解析js,這樣的話只要app.js已經(jīng)加載成功了,那么去同目錄下找bar.js就肯定能找到了。這種配置在定義模塊的時(shí)候非常有意義,這樣你的模塊就不依賴(lài)于放置這些模塊的文件夾名稱(chēng)了。
4.06 RJ的異步加載
RJ不管是require方法還是define方法的依賴(lài)模塊都是異步加載的,所以下面的代碼不一定能解析到正確的JS文件:
<script data-main="scripts/main" src="scripts/require.js"></script> <script src="scripts/other.js"></script> //main.js require.config({ paths: { foo: 'libs/foo-1.1.3' } }); //other.js require( ['foo'], function( foo ) { //foo is undefined });
由于main.js是異步加載的,所以other.js會(huì)比它先加載,但是RJ的配置存在于main.js里面,所以在加載other.js讀不到RJ的配置,在other.js執(zhí)行的時(shí)候解析出來(lái)的foo的路徑就會(huì)變成scripts/foo.js,而正確路徑應(yīng)該是scripts/libs/foo-1.1.3.js。
盡管RJ的依賴(lài)是異步加載的,但是已加載的模塊在多次依賴(lài)的時(shí)候,不會(huì)再重新加載:
define(['require', 'app/bar', 'app/app'], function(require) { var bar= require("app/bar"); var app= require("app/app"); //use bar and app do sth });
上面的代碼,在callback定義的時(shí)候,只用了一個(gè)形參,這主要是為了減少形參的數(shù)量,避免整個(gè)回調(diào)的簽名很長(zhǎng)。依賴(lài)的模塊在回調(diào)內(nèi)部可以直接用require(moduleID)的參數(shù)得到,由于在回調(diào)執(zhí)行前,依賴(lài)的模塊已經(jīng)加載,所以此處調(diào)用不會(huì)再重新加載。但是如果此處獲取一個(gè)并不在依賴(lài)數(shù)組中出現(xiàn)的module ID,require很有可能獲取不到該模塊引用,因?yàn)樗赡苄枰匦录虞d,如果它沒(méi)有在其它模塊中被加載過(guò)的話。
4.07 RJ官方推薦的JS文件組織結(jié)構(gòu)
RJ建議,文件組織盡量扁平,不要多層嵌套,最理想的是跟項(xiàng)目相關(guān)的放在一個(gè)文件夾,第三方庫(kù)放在一個(gè)文件夾,如下所示:
4.08 使用define定義模塊
AMD規(guī)定的模塊定義規(guī)范為:
define(id?, dependencies?, factory); 其中: id: 模塊標(biāo)識(shí),可以省略。 dependencies: 所依賴(lài)的模塊,可以省略。 factory: 模塊的實(shí)現(xiàn),或者一個(gè)JavaScript對(duì)象
關(guān)于第一個(gè)參數(shù),本文不會(huì)涉及,因?yàn)镽J建議所有模塊都不要使用第一個(gè)參數(shù),如果使用第一個(gè)參數(shù)定義的模塊成為命名模塊,不適用第一個(gè)參數(shù)的模塊成為匿名模塊,命名模塊如果更名,所有依賴(lài)它的模塊都得修改!第二個(gè)參數(shù)是依賴(lài)數(shù)組,跟require一樣,如果沒(méi)有這個(gè)參數(shù),那么定義的就是一個(gè)無(wú)依賴(lài)的模塊;最后一個(gè)參數(shù)是回調(diào)或者是一個(gè)簡(jiǎn)單對(duì)象,在模塊加載完畢后調(diào)用,當(dāng)然沒(méi)有第二個(gè)參數(shù),最后一個(gè)參數(shù)也會(huì)調(diào)用。
本部分所舉例都采用如下項(xiàng)目結(jié)構(gòu):
1. 定義簡(jiǎn)單對(duì)象模塊:
app/bar.js
define({ bar:'I am bar.' }); 利用main.js測(cè)試: require.config({ baseUrl: 'scripts/lib', paths: { app: '../app' } }); require(['app/bar'], function(bar) { console.log(bar);// {bar: 'I am bar.'} });
2. 定義無(wú)依賴(lài)的模塊:
app/nodec.js:
define(function () { return { nodec: "yes, I don't need dependence." } });
利用main.js測(cè)試:
require.config({ baseUrl: 'scripts/lib', paths: { app: '../app' } }); require(['app/nodec'], function(nodec) { console.log(nodec);// {nodec: yes, I don't need dependence.'} });
3. 定義依賴(lài)其它模塊的模塊:
app/dec.js:
define(['jquery'], function($){ //use $ do sth ... return { useJq: true } });
利用main.js測(cè)試:
require.config({ baseUrl: 'scripts/lib', paths: { app: '../app' } }); require(['app/dec'], function(dec) { console.log(dec);//{useJq: true} });
4. 循環(huán)依賴(lài):
當(dāng)一個(gè)模塊foo的依賴(lài)數(shù)組中存在bar,bar模塊的依賴(lài)數(shù)組中存在foo,就會(huì)形成循環(huán)依賴(lài),稍微修改下bar.js和foo.js如下。
app/bar.js:
define(['foo'],function(foo){ return { name: 'bar', hi: function(){ console.log('Hi! ' + foo.name); } } });
lib/foo.js:
define(['app/bar'],function(bar){ return { name: 'foo', hi: function(){ console.log('Hi! ' + bar.name); } } });
利用main.js測(cè)試:
require.config({ baseUrl: 'scripts/lib', paths: { app: '../app' } }); require(['app/bar', 'foo'], function(bar, foo) { bar.hi(); foo.hi(); });
運(yùn)行結(jié)果:
如果改變main.js中require部分的依賴(lài)順序,結(jié)果:
循環(huán)依賴(lài)導(dǎo)致兩個(gè)依賴(lài)的module之間,始終會(huì)有一個(gè)在獲取另一個(gè)的時(shí)候,得到undefined。解決方法是,在定義module的時(shí)候,如果用到循環(huán)依賴(lài)的時(shí)候,在define內(nèi)部通過(guò)require重新獲取。main.js不變,bar.js改成:
define(['require', 'foo'], function(require, foo) { return { name: 'bar', hi: function() { foo = require('foo'); console.log('Hi! ' + foo.name); } } });
foo.js改成:
define(['require', 'app/bar'], function(require, bar) { return { name: 'foo', hi: function() { bar = require('app/bar'); console.log('Hi! ' + bar.name); } } });
利用上述代碼,重新執(zhí)行,結(jié)果是:
模塊定義總結(jié):不管模塊是用回調(diào)函數(shù)定義還是簡(jiǎn)單對(duì)象定義,這個(gè)模塊輸出的是一個(gè)引用,所以這個(gè)引用必須是有效的,你的回調(diào)不能返回undefined,但是不局限于對(duì)象類(lèi)型,還可以是數(shù)組,函數(shù),甚至是基本類(lèi)型,只不過(guò)如果返回對(duì)象,你能通過(guò)這個(gè)對(duì)象組織更多的接口。
4.09 內(nèi)置的RJ模塊
再看看這個(gè)代碼:
define(['require', 'app/bar'], function(require) { return { name: 'foo', hi: function() { var bar = require('app/bar'); console.log('Hi! ' + bar.name); } } });
依賴(lài)數(shù)組中的require這個(gè)moduleID對(duì)應(yīng)的是一個(gè)內(nèi)置模塊,利用它加載模塊,怎么用你已經(jīng)看到了,比如在main.js中,在define中。另外一個(gè)內(nèi)置模塊是module,這個(gè)模塊跟RJ的另外一個(gè)配置有關(guān),具體用法請(qǐng)?jiān)诘?大部分去了解。
4.10 其它RJ有用功能
1. 生成相對(duì)于模塊的URL地址
define(["require"], function(require) { var cssUrl = require.toUrl("./style.css"); });
這個(gè)功能在你想要?jiǎng)討B(tài)地加載一些文件的時(shí)候有用,注意要使用相對(duì)路徑。
2. 控制臺(tái)調(diào)試
require("module/name").callSomeFunction()
假如你想在控制臺(tái)中查看某個(gè)模塊都有哪些方法可以調(diào)用,如果這個(gè)模塊已經(jīng)在頁(yè)面加載的時(shí)候通過(guò)依賴(lài)被加載過(guò)后,那么就可以用以上代碼在控制臺(tái)中做各種測(cè)試了。
5. RequireJS常用配置總結(jié)
在RJ的配置中,前面已經(jīng)接觸到了baseUrl,paths,另外幾個(gè)常用的配置是:
- shim
- config
- enforceDefine
- urlArgs
5.01 shim
為那些沒(méi)有使用define()來(lái)聲明依賴(lài)關(guān)系、設(shè)置模塊的"瀏覽器全局變量注入"型腳本做依賴(lài)和導(dǎo)出配置。
例1:利用exports將模塊的全局變量引用與RequireJS關(guān)聯(lián)
main.js如下:
require.config({ baseUrl: 'scripts/lib', paths: { app: '../app' }, shim: { underscore: { exports: '_' } } }); require(['underscore'], function(_) { // 現(xiàn)在可以通過(guò)_調(diào)用underscore的api了 });
如你所見(jiàn),RJ在shim中添加了一個(gè)對(duì)underscore這個(gè)模塊的配置,并通過(guò)exports屬性指定該模塊暴露的全局變量,以便RJ能夠?qū)@些模塊統(tǒng)一管理。
例2:利用deps配置js模塊的依賴(lài)
main.js如下:
require.config({ baseUrl: 'scripts/lib', paths: { app: '../app' }, shim: { backbone: { deps: ['underscore', 'jquery'], exports: 'Backbone' } } }); require(['backbone'], function(Backbone) { //use Backbone's API });
由于backbone這個(gè)組件依賴(lài)jquery和underscore,所以可以通過(guò)deps屬性配置它的依賴(lài),這樣backbone將會(huì)在另外兩個(gè)模塊加載完畢之后才會(huì)加載。
例3:jquery等庫(kù)插件配置方法
代碼舉例如下:
requirejs.config({ shim: { 'jquery.colorize': { deps: ['jquery'], exports: 'jQuery.fn.colorize' }, 'jquery.scroll': { deps: ['jquery'], exports: 'jQuery.fn.scroll' }, 'backbone.layoutmanager': { deps: ['backbone'] exports: 'Backbone.LayoutManager' } } });
5.02 config
常常需要將配置信息傳給一個(gè)模塊。這些配置往往是application級(jí)別的信息,需要一個(gè)手段將它們向下傳遞給模塊。在RequireJS中,基于requirejs.config()的config配置項(xiàng)來(lái)實(shí)現(xiàn)。要獲取這些信息的模塊可以加載特殊的依賴(lài)“module”,并調(diào)用module.config()。
例1:在requirejs.config()中定義config,以供其它模塊使用
requirejs.config({ config: { 'bar': { size: 'large' }, 'baz': { color: 'blue' } } });
如你所見(jiàn),config屬性中的bar這一節(jié)是在用于module ID為bar這個(gè)模塊的,baz這一節(jié)是用于module ID為baz這個(gè)模塊的。具體使用以bar.js舉例:
define(['module'], function(module) { //Will be the value 'large'var size = module.config().size; });
前面提到過(guò),RJ的內(nèi)置模塊除了require還有一個(gè)module,用法就在此處,通過(guò)它可以來(lái)加載config的內(nèi)容。
5.03 enforceDefine
如果設(shè)置為true,則當(dāng)一個(gè)腳本不是通過(guò)define()定義且不具備可供檢查的shim導(dǎo)出字串值時(shí),就會(huì)拋出錯(cuò)誤。這個(gè)屬性可以強(qiáng)制要求所有RJ依賴(lài)或加載的模塊都要通過(guò)define或者shim被RJ來(lái)管理,同時(shí)它還有一個(gè)好處就是用于錯(cuò)誤檢測(cè)。
5.04 urlArgs
RequireJS獲取資源時(shí)附加在URL后面的額外的query參數(shù)。作為瀏覽器或服務(wù)器未正確配置時(shí)的“cache bust”手段很有用。使用cache bust配置的一個(gè)示例:
urlArgs: "bust=" + (new Date()).getTime()
6. 錯(cuò)誤處理
6.01 加載錯(cuò)誤的捕獲
IE中捕獲加載錯(cuò)誤不完美:
IE 6-8中的script.onerror無(wú)效。沒(méi)有辦法判斷是否加載一個(gè)腳本會(huì)導(dǎo)致404錯(cuò);更甚地,在404中依然會(huì)觸發(fā)state為complete的onreadystatechange事件。
IE 9+中script.onerror有效,但有一個(gè)bug:在執(zhí)行腳本之后它并不觸發(fā)script.onload事件句柄。因此它無(wú)法支持匿名AMD模塊的標(biāo)準(zhǔn)方法。所以script.onreadystatechange事件仍被使用。但是,state為complete的onreadystatechange事件會(huì)在script.onerror函數(shù)觸發(fā)之前觸發(fā)。
所以為了支持在IE中捕獲加載錯(cuò)誤,需要配置enforceDefine為true,這不得不要求你所有的模塊都用define定義,或者用shim配置RJ對(duì)它的引用。
注意:如果你設(shè)置了enforceDefine: true,而且你使用data-main=""來(lái)加載你的主JS模塊,則該主JS模塊必須調(diào)用define()而不是require()來(lái)加載其所需的代碼。主JS模塊仍然可調(diào)用require/requirejs來(lái)設(shè)置config值,但對(duì)于模塊加載必須使用define()。比如原來(lái)的這段就會(huì)報(bào)錯(cuò):
require.config({ enforceDefine: true, baseUrl: 'scripts/lib', paths: { app: '../app' }, shim: { backbone: { deps: ['underscore', 'jquery'], exports: 'Backbone' } } }); require(['backbone'], function(Backbone) { console.log(Backbone); });
把最后三行改成:
define(['backbone'], function(Backbone) { console.log(Backbone); });
才不會(huì)報(bào)錯(cuò)。
6.02 paths備錯(cuò)
requirejs.config({ //To get timely, correct error triggers in IE, force a define/shim exports check. enforceDefine: true, paths: { jquery: [ 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min', //If the CDN location fails, load from this location 'lib/jquery' ] } }); //Later require(['jquery'], function ($) { });
上述代碼先嘗試加載CDN版本,如果出錯(cuò),則退回到本地的lib/jquery.js。
注意: paths備錯(cuò)僅在模塊ID精確匹配時(shí)工作。這不同于常規(guī)的paths配置,常規(guī)配置可匹配模塊ID的任意前綴部分。備錯(cuò)主要用于非常的錯(cuò)誤恢復(fù),而不是常規(guī)的path查找解析,因?yàn)槟窃跒g覽器中是低效的。
6.03 全局 requirejs.onError
為了捕獲在局域的errback中未捕獲的異常,你可以重載requirejs.onError():
requirejs.onError = function (err) { console.log(err.requireType); if (err.requireType === 'timeout') { console.log('modules: ' + err.requireModules); } throw err; };
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
- 教你5分鐘學(xué)會(huì)用requirejs(必看篇)
- RequireJS入門(mén)一之實(shí)現(xiàn)第一個(gè)例子
- RequireJS多頁(yè)面應(yīng)用實(shí)例分析
- 在Html中使用Requirejs進(jìn)行模塊化開(kāi)發(fā)實(shí)例詳解
- RequireJS使用注意細(xì)節(jié)
- 使用requirejs模塊化開(kāi)發(fā)多頁(yè)面一個(gè)入口js的使用方式
- requirejs + vue 項(xiàng)目搭建詳解
- 在JavaScript應(yīng)用中使用RequireJS來(lái)實(shí)現(xiàn)延遲加載
- 詳解RequireJS按需加載樣式文件
- 使用RequireJS庫(kù)加載JavaScript模塊的實(shí)例教程
- RequireJS用法簡(jiǎn)單示例
相關(guān)文章
JS實(shí)現(xiàn)左右拖動(dòng)改變內(nèi)容顯示區(qū)域大小的方法
這篇文章主要介紹了JS實(shí)現(xiàn)左右拖動(dòng)改變內(nèi)容顯示區(qū)域大小的方法,涉及JavaScript實(shí)時(shí)響應(yīng)鼠標(biāo)事件動(dòng)態(tài)改變頁(yè)面元素屬性的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10微信頁(yè)面倒計(jì)時(shí)代碼(解決safari不兼容date的問(wèn)題)
本文主要分享了微信頁(yè)面倒計(jì)時(shí)代碼(pc端),并在文章結(jié)尾分析了safari不兼容date的原因以及解決方法,具有很好的參考價(jià)值,需要的朋友一起來(lái)看下吧2016-12-12javascript設(shè)計(jì)模式之策略模式學(xué)習(xí)筆記
這篇文章主要介紹了javascript設(shè)計(jì)模式之策略模式學(xué)習(xí)筆記,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02JS實(shí)現(xiàn)簡(jiǎn)單網(wǎng)頁(yè)倒計(jì)時(shí)器
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)簡(jiǎn)單網(wǎng)頁(yè)倒計(jì)時(shí)器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08利用Angularjs和Bootstrap前端開(kāi)發(fā)案例實(shí)戰(zhàn)
這篇文章主要為大家介紹了利用Angularjs和Bootstrap前端開(kāi)發(fā)案例實(shí)戰(zhàn),感興趣的小伙伴們可以參考一下2016-08-08