javascript
requireJS 从概念到实战
?
requireJS 可以很輕易的將一個(gè)項(xiàng)目中的JavaScript代碼分割成若干個(gè)模塊(module)。并且requireJS推薦一個(gè)模塊就是一個(gè)文件,所以,你將獲得一些零碎的具有互相依賴關(guān)系的JS文件。模塊化的好處也淺顯意見,那就是大大增強(qiáng)代碼的可讀性、易維護(hù)性、可擴(kuò)展性、減少全局污染等。
目錄:
基本概念
requireJS的歷史發(fā)展
模塊化的優(yōu)點(diǎn)
require 實(shí)戰(zhàn)
????引入requireJS
????參數(shù)配置
????加載配置文件
????定義模塊
????簡單的值對
????非依賴的函數(shù)式定義
????依賴的函數(shù)式定義
????載入模塊
????模塊的返回值
??????????return 方式
??????????exports導(dǎo)出
????非標(biāo)準(zhǔn)模塊定義
????常用參數(shù)
??????????urlArgs
??????????scriptType
??????????waitSeconds
??????????deps
??????????callback
??????????config
??????????map
??????????packages
rquire 壓縮
其它問題
????1. timeout超時(shí)問題
????2. 循環(huán)依賴問題
????3. CDN回退
????4. 定義AMD插件
????5. 關(guān)于require的預(yù)定義模塊
????6. 關(guān)于R.js壓縮非本地文件的問題
????7. 關(guān)于R.js - shim功能的說明
????8. 關(guān)于require加載CSS的問題
基本概念
因?yàn)樽陨碓O(shè)計(jì)的不足,JavaScript 這門語言實(shí)際上并沒有模塊化這種概念與機(jī)制,所以想實(shí)現(xiàn)如JAVA,PHP等一些后臺語言的模塊化開發(fā),那么我們必須借助 requireJS 這個(gè)前端模擬模塊化的插件,雖然我們不需要去了解它的實(shí)現(xiàn)原理,但是大致去了解它是如何工作的,我相信這會(huì)讓我們更容易上手。
requireJS使用head.appendChild()將每一個(gè)依賴加載為一個(gè)script標(biāo)簽。 requireJS等待所有的依賴加載完畢,計(jì)算出模塊定義函數(shù)正確調(diào)用順序,然后依次調(diào)用它們。requireJS的歷史發(fā)展
在說JavaScript模塊化之前,我們不得不提CommonJS(原名叫ServerJs),這個(gè)社區(qū)可謂大牛云集,他們?yōu)镹odeJS制定過模塊化規(guī)范 Modules/1.0 ,并得到了廣泛的支持。在為JavaScript定制模塊化規(guī)范時(shí),討論的都是在 Modules/1.0 上進(jìn)行改進(jìn),但是 Modules/1.0 是專門為服務(wù)端制定的規(guī)范,所以要想套用在客服端環(huán)境的JS上,情況就會(huì)有很大的不同,例如,對于服務(wù)端加載一個(gè)JS文件,其耗費(fèi)的時(shí)間幾乎都是可以忽略不計(jì)的,因?yàn)檫@些都是基于本地環(huán)境,而在客戶端瀏覽器上加載一個(gè)文件,都會(huì)發(fā)送一個(gè)HTTP請求,并且還可能會(huì)存在跨域的情況,也就是說資源的加載,到執(zhí)行,是會(huì)存在一定的時(shí)間消耗與延遲。
所以社區(qū)的成員們意識到,要想在瀏覽器環(huán)境中也能模塊化開發(fā),則需要對現(xiàn)有規(guī)范進(jìn)行更改,而就在社區(qū)討論制定規(guī)范的時(shí)候內(nèi)部發(fā)生了比較大的分歧,分裂出了三個(gè)主張,漸漸的形成三個(gè)不同的派別:
1.Modules/1.x派 這一波人認(rèn)為,在現(xiàn)有基礎(chǔ)上進(jìn)行改進(jìn)即可滿足瀏覽器端的需要,既然瀏覽器端需要function包裝,需要異步加載,那么新增一個(gè)方案,能把現(xiàn)有模塊轉(zhuǎn)化為適合瀏覽器端的就行了,有點(diǎn)像“保皇派”。基于這個(gè)主張,制定了Modules/Transport(http://wiki.commonjs.org/wiki/Modules/Transport)規(guī)范,提出了先通過工具把現(xiàn)有模塊轉(zhuǎn)化為復(fù)合瀏覽器上使用的模塊,然后再使用的方案。 browserify就是這樣一個(gè)工具,可以把nodejs的模塊編譯成瀏覽器可用的模塊。(Modules/Transport規(guī)范晦澀難懂,我也不確定browserify跟它是何關(guān)聯(lián),有知道的朋友可以講一下) 目前的最新版是Modules/1.1.1(http://wiki.commonjs.org/wiki/Modules/1.1.1),增加了一些require的屬性,以及模塊內(nèi)增加module變量來描述模塊信息,變動(dòng)不大。 2. Modules/Async派 這一波人有點(diǎn)像“革新派”,他們認(rèn)為瀏覽器與服務(wù)器環(huán)境差別太大,不能沿用舊的模塊標(biāo)準(zhǔn)。既然瀏覽器必須異步加載代碼,那么模塊在定義的時(shí)候就必須指明所依賴的模塊,然后把本模塊的代碼寫在回調(diào)函數(shù)里。模塊的加載也是通過下載-回調(diào)這樣的過程來進(jìn)行,這個(gè)思想就是AMD的基礎(chǔ),由于“革新派”與“保皇派”的思想無法達(dá)成一致,最終從CommonJs中分裂了出去,獨(dú)立制定了瀏覽器端的js模塊化規(guī)范AMD(Asynchronous Module Definition)(https://github.com/amdjs/amdjs-api/wiki/AMD) 3. Modules/2.0派 這一波人有點(diǎn)像“中間派”,既不想丟掉舊的規(guī)范,也不想像AMD那樣推到重來。他們認(rèn)為,Modules/1.0固然不適合瀏覽器,但它里面的一些理念還是很好的,(如通過require來聲明依賴),新的規(guī)范應(yīng)該兼容這些,AMD規(guī)范也有它好的地方(例如模塊的預(yù)先加載以及通過return可以暴漏任意類型的數(shù)據(jù),而不是像commonjs那樣exports只能為object),也應(yīng)采納。最終他們制定了一個(gè)Modules/Wrappings(http://wiki.commonjs.org/wiki/Modules/Wrappings)規(guī)范,此規(guī)范指出了一個(gè)模塊應(yīng)該如何“包裝”,包含以下內(nèi)容:實(shí)際上這三個(gè)流派誰都沒有勝過誰,反而是最后的AMD,CMD 規(guī)范扎根在這三個(gè)流派之上,吸取它們提出的優(yōu)點(diǎn)不斷得到壯大。
總的來說AMD,CMD都是從commonJS規(guī)范中結(jié)合瀏覽器現(xiàn)實(shí)情況,并且吸收三大流派的優(yōu)點(diǎn)而誕生。其中CMD是國內(nèi)大牛制定的規(guī)范,其實(shí)現(xiàn)的工具是seaJS,而AMD則是國外大牛制定的,其實(shí)現(xiàn)技術(shù)則是requireJS
模塊化的優(yōu)點(diǎn)
既然我們已經(jīng)詳細(xì)的了解了“前端模塊化”的歷史與發(fā)展,那么我們也要大致了解模塊開發(fā)的好處,畢竟這是我們學(xué)習(xí)的動(dòng)力。
1. 作用域污染小明定義了 var name = 'xiaoming';N ~ 天之后:小王又定義了一個(gè) var name = 'xiaowang'; 2. 防止代碼暴漏可被修改: 為了解決全局變量的污染,早期的前端的先驅(qū)們則是以對象封裝的方式來寫JS代碼: var utils = { 'version':'1.3' }; 然而這種方式不可以避免的是對象中的屬性可被直接修改:utils.version = 2.0 。 3. 維護(hù)成本的提升。 如果代碼毫無模塊化可言,那么小明今天寫的代碼,若干天再讓小明自己去看,恐怕也無從下手。 4. 復(fù)用與效率 模塊與非模塊的目的就是為了復(fù)用,提高效率總的來說,前端的模塊化就是在眼瞎與手殘的過程進(jìn)行發(fā)展的,大致我們可以總結(jié)一下幾時(shí)代:
我們相信未來必將更加光明,但是回顧現(xiàn)在,特別是在國內(nèi)的市場環(huán)境中IE瀏覽器依然占據(jù)半壁江山,所以基于ES6的模塊特性依然任重道遠(yuǎn),因此,在光明還未播撒的時(shí)刻,就讓我們率先點(diǎn)燃一朵火苗照亮自己,而這朵火苗就是 ———— requireJS
require 實(shí)戰(zhàn)
下面我將化整為零的去講解requireJS在一個(gè)項(xiàng)目的具體使用方式以及需要注意的事項(xiàng)。
引入requireJS
通過?<script>?標(biāo)簽,將require.js 文件引入到當(dāng)前的 HTML 頁面中
參數(shù)配置
requireJS 常用的方法與命令也就兩個(gè),因此requireJS使用起來非常簡單。
- require
- define
其中define是用于定義模塊,而require是用于載入模塊以及載入配置文件。
在requireJS中一個(gè)文件就是一個(gè)模塊,并且文件名就是該模塊的ID,其表現(xiàn)則是以key/value的鍵值對格式,key即模塊的名稱(模塊ID),而value則是文件(模塊)的地址,因此多個(gè)模塊便有多個(gè)鍵值對值,這些鍵值對再加上一些常用的參數(shù),便是require的配置參數(shù),這些配置參數(shù)我們通常會(huì)單獨(dú)保存在一個(gè)JS文件中,方便以后修改、調(diào)用,所以這個(gè)文件我們也稱之為“配置文件”。
下面是requireJS的基本參數(shù)配置:
//index.html <script> require.config({ baseUrl:'js/', paths:{ 'jquery':'http://xxxx.xxx.com/js/jquery.min', 'index':'index' } }); require(['index']); </script>require.config()?是用于配置參數(shù)的核心方法,它接收一個(gè)有固定格式與屬性的對象作為參數(shù),這個(gè)對象便是我們的配置對象。
在配置對象中?baseUrl?定義了基準(zhǔn)目錄,它會(huì)與?paths中模塊的地址自動(dòng)進(jìn)行拼接,構(gòu)成該模塊的實(shí)際地址,并且當(dāng)配置參數(shù)是通過script標(biāo)簽嵌入到html文件中時(shí),baseUrl默認(rèn)的指向路徑就是該html文件所處的地址。
paths?屬性的值也是一個(gè)對象,該對象保存的就是模塊key/value值。其中key便是模塊的名稱與ID,一般使用文件名來命名,而value則是模塊的地址,在requireJS中,當(dāng)模塊是一個(gè)JS文件時(shí),是可以省略 .js 的擴(kuò)展名,比如 “index.js” 就可以直接寫成 “index” 而當(dāng)定義的模塊不需要與?baseUrl?的值進(jìn)行拼接時(shí),可以通過?"/"?與?http://?以及?.js?的形式來繞過?baseUrl的設(shè)定。
示例:
實(shí)際上,除了可以在require.js加載完畢后,通過require.config()方法去配置參數(shù),我們也可以在require.js加載之前,定義一個(gè)全局的對象變量 require 來事先定義配置參數(shù)。然后在require.js被瀏覽器加載完畢后,便會(huì)自動(dòng)繼承之前配置的參數(shù)。
<script>var require = { baseUrl: 'js/', paths: { 'jquery': 'http://xxx.xxxx.com/js/jquery.min', 'index': 'index' }, deps:[index] }; </script> <script src="js/require.js"></script>不論是在require.js加載之前定義配置參數(shù),還是之后來定義,這都是看看我們需求而言的,這里我們舉例的配置參數(shù)都是放入到script標(biāo)簽中,然后嵌入到HTML頁面的內(nèi)嵌方式,在實(shí)際使用時(shí),我們更多的則是將該段配置提取出來單獨(dú)保存在一個(gè)文件中,并將其取名為?app.js?,而這個(gè)?app.js?便是我們后面常說到的配置文件。
另外還有一個(gè)“接口文件”的概念,requireJS中,所謂接口文件指的便是require.js加載完畢后第一個(gè)加載的模塊文件。
加載配置文件
現(xiàn)在我們知道require的配置有兩種加載方式,一種是放入到script標(biāo)簽嵌入到html文件中,另一種則是作為配置文件?app.js?來獨(dú)立的引入。
獨(dú)立的引入配置文件也有兩種方式,一種是通過script標(biāo)簽加載外部JS文件形式:
另一種方式則是使用 require 提供的?data-main?屬性,該屬性是直接寫在引入require.js的script標(biāo)簽上,在require.js 加載完畢時(shí),會(huì)自動(dòng)去加載配置文件 app.js。
html<script data-main="js/app" src="js/require.js"></script>
通過?data-main?去加載入口文件,便會(huì)使配置對象中的?baseUrl?屬性默認(rèn)指向地址改為 app.js 所在的位置,相比之下我更加推薦這種方式,因?yàn)樗赡艿姆奖憧旖荨?/p>
當(dāng)我們的項(xiàng)目足夠的龐大時(shí),我也會(huì)推薦將入口文件作為一個(gè)普通的模塊,然后在這個(gè)模塊中,根據(jù)業(yè)務(wù)的不同再去加載不同的配置文件。
//define.js define(['app1','app2','app3','app4'],function(app1,app2,app3,app4){ if(page == 'app1'){ require.config(app1); }else if(page == 'app2'){ require.config(app2); }else if(page == 'app3'){ require.config(app3); }else{ require.config(app4); } })當(dāng)然關(guān)于模塊的定義和載入我們后面會(huì)詳細(xì)的講解到,這里只需要有一個(gè)概念即可。
定義模塊
在我們選擇requireJS來模塊化開發(fā)我們的項(xiàng)目或者頁面時(shí),就要明確的知道我們以后所編寫的代碼或者是某段功能,都是要放在一個(gè)個(gè)定義好的模塊中。
下面是requireJS定義模塊的方法格式:
define([id,deps,] callback);
ID:模塊的ID,默認(rèn)的便是文件名,一般無需使用者自己手動(dòng)指定。
deps:當(dāng)前模塊所以依賴的模塊數(shù)組,數(shù)組的每個(gè)數(shù)組元素便是模塊名或者叫模塊ID。
callback:模塊的回調(diào)方法,用于保存模塊具體的功能與代碼,而這個(gè)回調(diào)函數(shù)又接收一個(gè)或者多個(gè)參數(shù),這些參數(shù)會(huì)與模塊數(shù)組的每個(gè)數(shù)組元素一一對應(yīng),即每個(gè)參數(shù)保存的是對應(yīng)模塊返回值。
根據(jù)?define()?使用時(shí)參數(shù)數(shù)量的不同,可以定義以下幾種模塊類型:
簡單的值對
當(dāng)所要定義的模塊沒有任何依賴也不具有任何的功能,只是單純的返回一組鍵值對形式的數(shù)據(jù)時(shí),便可以直接將要返回的數(shù)據(jù)對象寫在?define方法中:
define({'color':'red','size':'13px', 'width':'100px' });這種只為保存數(shù)據(jù)的模塊,我們稱之為“值對”模塊,實(shí)際上值對模塊不僅可以用于保存數(shù)據(jù),還可以保存我們的配置參數(shù),然后在不同的業(yè)務(wù)場景下去加載不同的配置參數(shù)文件。
示例:
//app1.js define({baseUrl:'music/js/', paths:{ msuic:'music', play:'play' } }); //app2.js define({baseUrl:'video/js/', paths:{ video:'video', play:'play' } });非依賴的函數(shù)式定義
如果一個(gè)模塊沒有任何的依賴,只是單純的執(zhí)行一些操作,那么便可以直接將函數(shù)寫在?define方法中:
define(function(require,exports,modules){// do something return { 'color':'red', 'size':'13px' } });依賴的函數(shù)式定義
這種帶有依賴的函數(shù)式模塊定義,也是我們平時(shí)常用到的,這里我們就結(jié)合實(shí)例,通過上面所舉的?index?模塊為例:
//index.js define(['jquery','./utils'], function($) { $(function() { alert($); }); });從上面的示例中我們可以看出?index?模塊中,依賴了 'jquery' 模塊,并且在模塊的回調(diào)函數(shù)中,通過?$?形參來接收?jquery模塊返回的值,除了?jquery?模塊,index模塊還依賴了?utils?模塊,因?yàn)樵撃K沒有在配置文件中定義,所以這里以附加路徑的形式單獨(dú)引入進(jìn)來的。
載入模塊
在說載入模塊之前,我們先聊聊“模塊依賴”。模塊與模塊之間存在著相互依賴的關(guān)系,因此就決定了不同的加載順序,比如模塊A中使用到的一個(gè)函數(shù)是定義在模塊B中的,我們就可以說模塊A依賴模塊B,同時(shí)也說明了在載入模塊時(shí),其順序也是先模塊A,再模塊B。
在require中,我們可以通過?require()?方法去載入模塊。其使用格式如下:
require(deps[,callback]);
deps:所要載入的模塊數(shù)組。
callback:模塊載入后執(zhí)行的回調(diào)方法。
這里就讓我們依然使用上述的 index 模塊為例來說明
示例:
requireJS 通過?require([])?方法去載入模塊,并執(zhí)行模塊中的回調(diào)函數(shù),其值是一個(gè)數(shù)組,數(shù)組中的元素便是要載入的模塊名稱也就是模塊ID,這里我們通過?require(['index'])?方法載入了 index 這個(gè)模塊,又因?yàn)樵撃K依賴了 jquery 模塊,所以接著便會(huì)繼續(xù)載入jquery模塊,當(dāng)jquery模塊加載完成后,便會(huì)將自身的方法傳遞給形參?$?最后執(zhí)行模塊的回調(diào)方法,alert出$參數(shù)具體內(nèi)容。
這里我們可以小小的總結(jié)一下,實(shí)現(xiàn)模塊的載入除了?require([],fn)?的主動(dòng)載入方法,通過依賴也可以間接載入對應(yīng)的模塊,但是相比較而言require方式載入模塊在使用上更加靈活,它不僅可以只載入模塊不執(zhí)行回調(diào),也可以載入模塊然后執(zhí)行回調(diào),還可以在所定義的模塊中,按需載入所需要用到的模塊,并且將模塊返回的對象或方法中保存在一個(gè)變量中,以供使用。
這種按需載入模塊,也叫就近依賴模式,它的使用要遵循一定的使用場景:
當(dāng)模塊是非依賴的函數(shù)式時(shí),可以直接使用
當(dāng)模塊是具有依賴的函數(shù)式時(shí),只能夠以回調(diào)的形式處理。
define(['jquery'], function($) { $(function() { require(['utils'],function(utils){ utils.sayHellow('Hellow World!'); }); }); });當(dāng)然聰明伶俐的你,一定會(huì)想到這樣更好的辦法:
define(['jquery','require','exports','modules'], function($,require,exports,modules) { $(function() { //方式一 require(['utils'],function(utils){ utils.sayHellow('Hellow World!'); }); //方式二: var utils = require('utils'); utils.sayHellow('hellow World') }); });模塊的返回值
require中定義的模塊不僅可以返回一個(gè)對象作為結(jié)果,還可以返回一個(gè)函數(shù)作為結(jié)果。實(shí)現(xiàn)模塊的返回值主要有兩種方法:
return 方式
// utils.jsdefine(function(require,exports,modules){ function sayHellow(params){ alert(params); } return sayHellow }); // index.js define(function(require,exports,modules){ var sayHellow = require('utils'); sayHellow('hellow World'); })如果通過return 返回多種結(jié)果的情況下:
// utils.jsdefine(function(require,exports,modules){ function sayHellow(params){ alert(params); } function sayBye(){ alert('bye-bye!'); } return { 'sayHellow':sayHellow, 'sayBye':sayBye } }); // index.js define(function(require,exports,modules){ var utils = require('utils'); utils.sayHellow('hellow World'); })exports導(dǎo)出
// utils.jsdefine(function(require,exports,modules){ function sayHellow(params){ alert(params); } exports.sayHellow = sayHellow; }) // index.js define(function(require,exports,modules){ var utils = require('utils'); utils.sayHellow('hellow World'); });這里有一個(gè)注意的地方,那就是非依賴性的模塊,可以直接在模塊的回調(diào)函數(shù)中,加入以下三個(gè)參數(shù):
require:加載模塊時(shí)使用。
exports:導(dǎo)出模塊的返回值。
modules:定義模塊的相關(guān)信息以及參數(shù)。
非標(biāo)準(zhǔn)模塊定義
在?require.config()?方法的配置對象中有一個(gè)?shim?屬性,它的值是一個(gè)對象,可以用于聲明非標(biāo)準(zhǔn)模塊的依賴和返回值。
所謂的 “非標(biāo)準(zhǔn)模塊” 指的是那些不符合的AMD規(guī)范的JS插件。
下面我們先看看基本的?shim?配置參數(shù):
這里需要注意的是如果所加載的模塊文件是符合AMD規(guī)范,比如通過?define?進(jìn)行定義的,那么require默認(rèn)的優(yōu)先級將是標(biāo)準(zhǔn)的,只有在不符合標(biāo)準(zhǔn)的情況下才會(huì)采用shim中定義的參數(shù)。
在 index 模塊執(zhí)行時(shí):
define(['jquery','tool','say'],function($,tool,say){ tool.drag(); say.sayHellow(); say.sayBye(); })上面的示例中,關(guān)于?shim?中有三個(gè)重要的屬性,它們分別是:
deps:?用于聲明當(dāng)前非標(biāo)準(zhǔn)模塊所依賴的其它模塊,值是一個(gè)數(shù)組,數(shù)組元素是模塊的名稱或者是ID。
exports:用于定義非標(biāo)準(zhǔn)模塊的全局變量或者方法。值一般是一個(gè)字符串。
init:用于初始,處理,非標(biāo)準(zhǔn)模塊的全局變量或者是方法,常用于當(dāng)非標(biāo)準(zhǔn)模塊存在多個(gè)全局變量以及方法,值是一個(gè)函數(shù)。
常用參數(shù)
在?require.config?中還存在其他的常用屬性設(shè)置。
urlArgs
RequireJS獲取資源時(shí)附加在URL后面的額外的query參數(shù)。作為瀏覽器或服務(wù)器未正確配置時(shí)的“cache bust”手段很有用。使用cache bust配置的一個(gè)示例:
javascript:;urlArgs: "bust=" + (new Date()).getTime()
在開發(fā)中這很有用,但請記得在部署到生成環(huán)境之前移除它。
scriptType
指定RequireJS將script標(biāo)簽插入document時(shí)所用的type=""值。默認(rèn)為“text/javascript”。想要啟用Firefox的JavaScript 1.8特性,可使用值“text/javascript;version=1.8”。
waitSeconds
通過該參數(shù)可以設(shè)置requireJS在加載腳本時(shí)的超時(shí)時(shí)間,它的默認(rèn)值是7,即如果一個(gè)腳本文件加載時(shí)長超過7秒鐘,便會(huì)放棄等待該腳本文件,從而報(bào)出timeout超時(shí)的錯(cuò)誤信息,考慮到國內(nèi)網(wǎng)絡(luò)環(huán)境不穩(wěn)定的因素,所以這里我建議設(shè)置為0。當(dāng)然一般不需要去改動(dòng)它,除非到了你需要的時(shí)候。
deps
用于聲明require.js在加載完成時(shí)便會(huì)自動(dòng)加載的模塊,值是一個(gè)數(shù)組,數(shù)組元素便是模塊名。
callback
當(dāng)deps中的自動(dòng)加載模塊加載完畢時(shí),觸發(fā)的回調(diào)函數(shù)。
config
config屬性可以為模塊配置額外的參數(shù)設(shè)定,其使用格式就是以模塊名或者模塊ID為key,然后具體的參數(shù)為value。
//app.js require.config({baseUrl:'js/', paths:{ 'jquery':'http://xx.xxxx.com/js/jquery.min', 'index':'index' }, config:{ 'index':{ 'size':13, 'color':'red' } } }); //index.js define(['jquery','module'],function($,module){ console.log(module.config().size) });這里要引起我們注意的地方就是依賴的'module'模塊,它是一個(gè)預(yù)先定義好的值,引入該值,在當(dāng)前模塊下便可以調(diào)用module對象,從該對象中執(zhí)行?config()?方法便可以生成改模塊的參數(shù)對象。
map
[略],暫時(shí)還未弄明白其具體使用方式,后續(xù)會(huì)繼續(xù)保持關(guān)注,如果你知曉其作用,麻煩你一定要與我聯(lián)系。
packages
[略],暫時(shí)還未弄明白其具體使用方式,后續(xù)會(huì)繼續(xù)保持關(guān)注,如果你知曉其作用,麻煩你一定要與我聯(lián)系。
rquire 壓縮
RequireJS 會(huì)將完整項(xiàng)目的JavaScript代碼輕易的分割成苦干個(gè)模塊(module),這樣,你將獲得一些具有互相依賴關(guān)系的JavaScript文件。在開發(fā)環(huán)境中,這種方式可以讓我們的代碼更具有模塊化與易維護(hù)性。但是,在生產(chǎn)環(huán)境中將所有的JavaScript文件分離,這是一個(gè)不好的做法。這會(huì)導(dǎo)致很多次請求(requests),即使這些文件都很小,也會(huì)浪費(fèi)很多時(shí)間。因此我們可以通過合并這些腳本文件壓縮文件的大小,以減少請求的次數(shù)與資源的體積來達(dá)到節(jié)省加載時(shí)間的目的,所以這里我們就要提到一個(gè)關(guān)于requireJS 延伸,那就是 r.js。
r.js 是一個(gè)獨(dú)立的項(xiàng)目,它作用在nodeJS環(huán)境中,可以實(shí)現(xiàn)對JS代碼的合并壓縮。
使用r.js 要具有以下幾個(gè)條件:
r.js 可以直接丟在項(xiàng)目的根目錄上,build.js 是 r.js 的配置文件,由開發(fā)者自己新建,與r.js同目錄。其一般的目錄結(jié)構(gòu)如下:
[project]/js/css/images index.html r.js build.jsr.js 下載
nodeJS環(huán)境,以及r.js都好辦,重要的就是掌握配置文件的使用 -- build.js,下面我們就詳細(xì)的說說它。
以上環(huán)節(jié)都準(zhǔn)備好了之后,就可以在終端中允許打包壓縮命令:?node r.js -o build.js。
當(dāng)執(zhí)行該命令后,r.js?會(huì)將自身所在目錄的所有資源連同目錄重新拷貝一份到輸出目錄(dir)中。然后再輸出目錄進(jìn)行最后的合并與壓縮操作。
其它問題
timeout超時(shí)問題
該問題一般是?waitSeconds?屬性值導(dǎo)致,解決的方法有兩個(gè),一個(gè)是將?waitSeconds的值設(shè)置更長時(shí)間,比如17s,另一個(gè)就是將其值設(shè)置為0,讓其永不超時(shí)。
循環(huán)依賴問題
何為循環(huán)依賴?
如果存在兩個(gè)模塊,moduleA 與 moduleB, 如果 moduleA 依賴 moduleB ,moduleB也依賴了moduleA,并且這中情況下,便是循環(huán)依賴。
循環(huán)依賴導(dǎo)致的問題!
如果兩個(gè)模塊循環(huán)依賴,并且A中有調(diào)用B中的方法,而B中也有調(diào)用A中的方法,那么此時(shí),A調(diào)用B正常,但是B中調(diào)用A方法,則會(huì)返回?undefined?異常。
如何解決循環(huán)依賴的問題?
通過?require([],fn)?解決
此時(shí)在模塊B中,我們通過引入 require 依賴,然后再通過?require()?方法去載入模塊A,并在回調(diào)中去執(zhí)行。
這里要引起我們注意的地方就是依賴的'module'模塊,它是一個(gè)預(yù)先定義好的值,引入該值,在當(dāng)前模塊下便可以調(diào)用?require?方法。
通過?exports?解決
define(['exports','jquery'],function(exports,$){ function bFunction(){ exports.aFunction(); alert('this is b module'); } exports.bFunction = bFunction; });相同的這里依賴的?module?模塊也是一個(gè)預(yù)先定義好的值,,引入該值,在當(dāng)前模塊下便可以調(diào)用?exports?對象設(shè)定當(dāng)前模塊的返回值。
而通過?exports?所解決的循環(huán)依賴問題,有一個(gè)需要注意的地方,那就是方法的執(zhí)行必須要放入到當(dāng)前定義方法的回調(diào)中,因?yàn)槲覀儾荒艽_定 moduleA 與 moduleB的加載順序。
CDN回退
如果我們不確定一個(gè)模塊的加載正確,我們可以在?require.config()方法中將模塊的地址替換為一個(gè)數(shù)組,數(shù)組的元素,便是同一模塊的多個(gè)地址。
requirejs.config({paths: {jquery: ['//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js', 'lib/jquery' ] } });定義AMD插件
有時(shí)候我們自己編寫的一款插件,我們需要它能夠在任何環(huán)境中都能起作用,比如在引入requireJS的AMD環(huán)境下可以作為符合AMD規(guī)范的插件,進(jìn)行模塊式加載調(diào)用,而在普通的瀏覽器環(huán)境中也可以正常的執(zhí)行。
想實(shí)現(xiàn)這一功能,其實(shí)很簡單,只需要按照下例格式去編寫插件即可。
關(guān)于require的預(yù)定義模塊
關(guān)于這個(gè)問題,我們上面也有說到,這里就進(jìn)行一次總結(jié)。
我們可以這樣理解,對于 requireJS 來說,除了我們自己使用require.config()定義的模塊,它內(nèi)部也有自己預(yù)先定義好的模塊,比如:require,exports,modules?,在使用時(shí),我們無需在?require.config()?去中定義,而是可以直接在依賴中引入使用,比如:
關(guān)于R.js壓縮非本地文件的問題
在?r.js?中是無法合并壓縮遠(yuǎn)程文件的,它只能操作本地文件,因此這就帶來一個(gè)問題,當(dāng)我們進(jìn)行模塊的壓縮合并時(shí),若某個(gè)模塊存在著對遠(yuǎn)程模塊(文件)的依賴時(shí),使用?r.js?進(jìn)行操作便會(huì)報(bào)錯(cuò),雖然可以將這個(gè)遠(yuǎn)程文件拷貝到本地來解決這一問題,但是如果像一些公用的資源例如JQ插件等,如果讓每個(gè)項(xiàng)目都在本地放入一個(gè)?common?資源包,這就脫離了我們的實(shí)際意義。
({paths:{jquery:'http://xxx.com/js/jquery.min'} })此時(shí)進(jìn)行打包的時(shí)候在就會(huì)報(bào)錯(cuò)。但是如果我們不在?paths?中去聲明?jquery模塊,當(dāng)打包的時(shí)候,r.js?發(fā)現(xiàn)其它模塊有依賴?jquery的,但是你又沒有在?build.js中聲明,依然會(huì)報(bào)錯(cuò)阻礙運(yùn)行。
那么有沒有一個(gè)好的辦法呢?比如雖然聲明了?jquery?模塊,但是值卻不是遠(yuǎn)程的文件,本地也不存在該文件,更不會(huì)報(bào)錯(cuò)。答案是有的,那就是對(不需要參與壓縮合并的)遠(yuǎn)程的資源模塊設(shè)置值為 empty:。 ```javascript:; ({ paths:{ jquery:'empty:' } }) ``` 或者是在執(zhí)行命令時(shí),指定參數(shù)值為空:node r.js -o build.js paths.jquery=empty:`
關(guān)于R.js - shim功能的說明
R.js 用于合并多個(gè)模塊(多個(gè)文件),以及壓縮文件中的JS代碼,也就是說在這個(gè)合并后的文件中會(huì)包含多個(gè)?define定義的模塊,而這個(gè)合并后的文件也就是這個(gè)頁面的入口文件,并且rquire的config配置也會(huì)在其中。
模塊的合并,對于R.js來言,它會(huì)以?build.js?中?paths屬性定義的模塊為參考,然后到要合并的模塊只能中去匹配依賴,匹配到了就合并到當(dāng)前文件中。如果參與合并的所有模塊有某些依賴順序上的調(diào)整,則可以通過?shim?屬性去調(diào)整合并時(shí)的前后順序。
//build.js ({'paths':{'a':'a', 'b':'b' }, 'shim':{ 'a':{ 'deps':['b'] } }, wrapShim:true })此時(shí)合并到主文件后,b?模塊的內(nèi)容就會(huì)在?a?模塊之前。
define('b',[],function(){}), define('a',[],function(){})最后強(qiáng)調(diào)一點(diǎn),對于通過?exclude?屬性排除合并的模塊,使用?shim?并不會(huì)產(chǎn)生作用,因?yàn)樗粚喜⒃谝粋€(gè)文件中的模塊有效。
關(guān)于require加載CSS的問題
requireJS不僅僅只加載JS文件,實(shí)際上它還可以加載CSS樣式文件,但是這需要借助一個(gè)requireJS插件才能實(shí)現(xiàn)。
下載地址:require-css.js
使用上,有兩種方式,一種在配置參數(shù)中進(jìn)行聲明:
var require = {baseUrl:'js/', paths:{ 'index':'index', 'a':'a' }, shim:{ 'a':{ deps:['css!../css/a.css'] } }, deps:['index'] }; //index.js define(['a']); // 載入模塊不執(zhí)行任何操作。另一種是直接在模塊中進(jìn)行依賴聲明
define(['css!../css/a.css']);最后說下我個(gè)人對?css!../css/index.css?的理解吧,首先?!?是插件與插件參數(shù)的分割符號,因此"css"就是插件的模塊名,requireJS會(huì)先去檢查?css?這個(gè)模塊是否有在配置文件中聲明,如果沒有則會(huì)默認(rèn)在?baseUrl?指向的路徑下去載入,而分隔符右邊的 '../css/a.css' 就是插件要使用的參數(shù),這里便是要載入的css文件地址。
http://requirejs.org/docs/api.html // 這是require的英文版官網(wǎng),建議來這里學(xué)習(xí),中文版太多翻譯問題。
https://segmentfault.com/a/1190000002401665 //對require的配置參數(shù)講解的很詳細(xì)
?
原文:https://www.cnblogs.com/HCJJ/p/6611669.html
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/ljbkyBlog/p/10190473.html
總結(jié)
以上是生活随笔為你收集整理的requireJS 从概念到实战的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 同余方程笔记
- 下一篇: gradle idea java ssm