一:為什么要使用requireJS?
很久之前,我們所有的JS文件寫到一個js文件里面去進行加載,但是當業務越來越復雜的時候,需要分成多個JS文件進行加載,比如在頁面中head內分別引入a.js,b.js,c.js等,如下所示:
<script src="js/app/a.js"></script> <script src="js/app/b.js"></script> <script src="js/app/c.js"></script>
我們現在先在瀏覽器下看看這些請求,如下所示:
這樣的寫法有如下缺點:
1. 頁面在加載的時候,是從頁面自上往下加載及渲染的,當頁面上有多個分散的js文件時候,頁面會先加載及解析頭部的JS文件(同步加載),頁面被堵塞了,其次分散的js請求數多了,網頁失去響應的時間就會變長。
2. 由於JS文件存在依賴關系,比如上面的b.js要依賴於a.js,所以務必保證a.js優先引入到頁面上來且先加載,要嚴格保證加載順序,依賴性最大的文件一定要放到最后加載。但是當依賴關系很復雜的時候,代碼的編寫和維護就會變得困難了。
當然上面引入JS時候,對於第1點:首先:我們可以放在底部去加載,把所有JS放在</body>之前去,這樣就會解決了游覽器堵塞的問題,其次我們可以把所有的JS文件打包成一個JS文件,但是依賴性(也就是順序)我們還是沒有辦法解決掉,所以我們引入了requireJS。
二:使用requireJS的優點有哪些?
1. 實現JS文件的異步加載,避免網頁被堵塞。
2. 管理模塊之間的依賴性,便於代碼的編寫和維護。
requireJS基本語法及使用.
1. 首先我們需要到官網下載最新版本的requireJS源碼包。下載地址:,
在頁面頭部head標簽內引入requireJS,如下:<script src="js/require.js"></script>,但是加載這個文件也會造成網頁失去響應,我們可以加上 defer 和 async這個屬性。如下:
<script src="js/require.js" defer async="true" ></script>
Async屬性表明文件需要異步加載,IE不支持這個屬性,只支持defer,所以上面把這2個屬性都加上。接下來,看看requireJS啟動加載腳本的初始化方式,requireJS支持屬性 data-main 這個屬性來加載初始化的JS文件,如下:
<script src="js/require.js" defer async="true" data-main="js/app.js"></script>
上面的意思是:先異步加載requireJS文件,完成后繼續異步加載app.js文件,假如app.js內容為空的話,我們可以看看加載順序如下:
上面的app.js后的.js可以去掉,因為requireJS源碼已經默認都是以后綴JS文件結尾的。
2. 如何定義模塊文件?
RequireJS編寫模塊不同於其他腳本文件,它良好的使用define來定義一個作用域避免全局空間污染,它可以顯示出其依賴關系,並以函數(定義此模塊的那個函數)參數的形式將這些依賴進行注入。
下面我們來看下demo,如下新建一個項目文件:
我們先在app/b.js下添加基本的requireJS代碼如下:
// b.js
define(function(){
var add = function(x,y) {
return x + y;
};
return {
add : add
}
});
使用define來定義模塊,下面我們需要在app.js里面來加載b.js模塊,如下在app.js里面來調用了。
require(['app/b'], function (b){
console.log(b.add(1,1));
});
我們接着看看文件加載的情況如下:
在head標簽內動態生成文件,如下:
可以看到加載順序 requirejs --> app.js --> b.js。
上面的是函數式的定義如上面方式編寫代碼(使用define定義一個函數),我們還可以編寫簡單的鍵值對,直接返回一個對象(可以解決全局變量的理念),我們現在在a.js里面返回這么一個對象,如下:
// a.js
define(function () {
return {
color: "black",
size: "unisize"
}
});
在app.js初始化代碼如下:
require(['app/a'],function(a){
console.log(a);
});
我們在控制台上可以看到如下:
直接返回一個對象,通過使用上面的方法我們可以想到可以解決全局變量概念,比如全局變量全部使用define函數包圍,什么時候需要全局變量的話,直接require([‘XX’],function(XX){})這樣調用下,同時所有的JS都是異步的,並不會堵塞加載。
3. AMD模塊規范
第一種寫法:
define(function() {
return {
mix: function(source, target) {
}
};
});
第二種寫法 有依賴項 如下:
define(['data', 'ui'], function(data, ui) {
// init here
});
第三種寫法 直接一個對象
define({
data: [],
ui: []
});
第四種寫法 具名模塊 如下:
define('index', ['data','base'], function(data, base) {
// todo
});
第五種寫法 包裝模塊 如下:
define(function(require, exports, module) {
var base = require('base');
exports.show = function() {
// todo with module base
}
});
書寫格式和nodeJS比較像,可以使用require獲取模塊,使用exports或者module.exports導出API。
當然requireJS是遵循AMD的規范的,所以一般情況下也具有上面的書寫代碼方式。
對於第四種寫法 具名模塊寫法我們並不推薦的,因為不書寫模塊名我們一樣可以調用,且在合並代碼的時候,我們也可以根據代碼自動生成模塊名,如果我們現在寫死了模塊名,當某個時候,b.js我要移動到其他目錄時候,JS也要跟着改,所以代碼維護方面不好,所以不建議書寫模塊名。對於第五種寫法,requireJS中也是支持的,通過內部調用require來處理依賴模塊,我們也可以試着做demo看看就知道了,還是app.js,我想初始化a.js代碼,我改成這樣的方式,如下代碼:
define(function(require, exports, module) {
var a = require('app/a');
console.log(a);
exports.show = function() {
// todo with module base
}
});
通過控制台也可以看到已經打印出 a 出來。
注意:1、 書寫requireJS遵循一個文件一個模塊。
2、 不要手動寫模塊名標示。
4. requireJS配置項如下:
1.baseUrl: 指定本地模塊的基准目錄,即本地模塊的路徑是相對於那個目錄的,該屬性通常有requireJS加載時的data-main屬性指定。比如如下代碼:
項目目錄結構還是上面的。
在頁面頂部<head>中引入 <script src="js/require.js" defer async="true" data-main="js/app"></script>
在app.js如下代碼:
requirejs.config({
baseUrl: 'js/app'
});
requirejs(['a','b','c'],function(a,b,c){
});
在瀏覽器頁面游覽可以看到如下請求:
如上可以看到,index.html和js是同一個目錄下的,都是放在requireJS文件夾里面的,所以定義baseUrl:’js/app’ 會自動解析成 requireJS/js/app/ 所以requirejs([‘a’,’b’,’c’])的話,會自動到requireJS/js/app/目錄下去查找a.js,b.js,c.js.找到了就可以加載出來。
如果未顯示設置baseUrl,則默認值是加載require.js的html所處的位置,如果使用了data-main屬性的話,則該路徑變成了baseUrl.如下代碼:
Index.html代碼如下:
<script src="js/require.js" defer async="true" data-main="js/app"></script>
App.js代碼如下:
requirejs(['a','b','c'],function(a,b,c){
});
那么在瀏覽器下會被解析成如下:
如上顯示:默認情況下是從data-main文件入口去加載js/app.js代碼的,但是現在app.js中並沒有設置config配置項,所以使用requirejs([‘a’,’b’,’c’],function(a,b,c))的時候會繼續加載js下面的a.js,b.js,c.js,如果找到就加載,沒有找到就顯示404 not found,如上所示。
2.paths: paths是映射那些不直接放在baseUrl指定的目錄下的文件,設置paths的起始位置是相對於baseUrl的,除非該path設置是以”/”開頭或含有URL協議(http://或者https://).
如下在app.js代碼:
requirejs.config({
baseUrl: 'js/lib',
paths: {
app: '../app'
}
});
requirejs(['app/a'],function(a){
});
在頁面上加載顯示如下:
可以看到paths是相對於baseUrl配置項生成的,baseUrl:’js/lib’下的所有js文件,但是paths下的 app:’../app’是相對於js/lib下設置的,’..’的解析到js目錄下,然后就解析成js/app下,再require([‘app/a’]),就解析到js/app/a.js了。
如果app.js代碼注釋掉baseUrl時,變成如下代碼:
requirejs.config({
//baseUrl: 'js/lib',
paths: {
app: '../app'
}
});
requirejs(['app/a'],function(a){ });
那么就被加載成這個樣子了,如下所示:
直接把app/a.js放在項目文件requirejs下了。
3. shim參數 解決了使用非AMD方式定義的模塊(如jquery插件)及其載入順序,為那些沒有使用define()來聲明依賴關系,設置模塊的”瀏覽器全局變量注入”型腳本做依賴和導出配置。
在js/app目錄下新建文件 depBase.js 代碼如下:
define(function(){
return {
"a":11
}
})
接着在app.js文件里面把代碼改成如下:
require.config({
baseUrl: 'js/lib',
shim: {
'app/depBase': ['jquery']
},
paths: {
app: '../app'
}
});
require(['app/depBase'],function(base){
console.log(base);
});
然后在瀏覽器查看請求如下:
由上面可以看到,我require(['app/depBase'],function(base){console.log(base);});這個,它先加載baseUrl中的配置 js/lib下的jquery文件,然后再加載js/app/depBase.js文件。也就是說shim這個參數可以解決沒有使用define(function(){})這樣的文件包圍的代碼或者一些全局變量注入,可以確保此文件先加載,然后再加載其他文件。
但是如果我不使用shim這個參數的話,在最新版的requirejs2.1.15中(以前的版本我不太清楚),也可以通過require([‘XX’])來解決,如下演示:
比如我在js/app文件下新建global.js文件,現在的目錄如下:
其中global.js代碼如下:
names = 1111;
創造一個全局變量names,其中js/app/depBase.js代碼變成如下:
define(function(){
return {
'name':names
}
})
也就是說我在app.js代碼如下初始化如下:
require.config({
baseUrl: 'js/app'
});
require(['global','depBase'],function(global,base){
console.log(base);
});
我先global初始化引入全局變量names,接着打印出depBase的返回值,截圖如下:
也可以看到,可以引入到全局變量names的值。
4.Map參數:Map參數是用來解決同一個模塊不同版本的問題,比如在項目開發中,開發初期使用了jquery1.7版本,但是由於業務的需求需要引入jquery1.9以上的版本時候,但是又擔心有些是依賴於jquery1.7的代碼升級到1.9以上的時候會有問題,因此可以讓一部分代碼還是依賴於jquery1.7,薪增的代碼依賴於jquery1.9.
下面我們來看看我們目錄結構如下所示:
我在lib文件下新增jquery1.7.js和 jquery1.9.1.js,現在我在入口文件app.js添加如下代碼:
requirejs.config({
map: {
'app/a': {
'jquery': 'js/lib/jquery1.7.js'
},
'app/b': {
'jquery': 'js/lib/jquery1.9.1.js'
}
}
});
require(['app/a'],function(jq){
});
require(['app/b'],function(jq){
});
然后在app/a.js添加如下代碼:
// a.js
define(function (require, exports, module) {
var a = require(['jquery']);
});
在app/b.js添加如下代碼:
// b.js
define(function (require, exports, module) {
var b = require(['jquery']);
});
在app.js中
require(['app/a'],function(jq){
});時候,在加載app/a.js的時候會加載jquery1.7.js文件,在加載app/b.js的時候會加載jquery1.9.1.js.如下截圖所示:
如果在app.js中把下面這行b.js代碼初始化注釋掉
require(['app/b'],function(jq){
});
那么就只會加載app/a.js及對應的jquery1.7.js,截圖如下:
相應的 如果把app/a.js初始化代碼注釋掉,把app/b.js代碼初始化打開,那么只會加載jquery1.9.1,可以看到如果我想app/b.js中使用jquery1.9的話,那么可以這樣使用了。
5.config參數。 config是指需要將配置信息傳給一個模塊,這些配置往往是application級別的信息,需要一個手段將他們向下傳遞給模塊。在requireJS中,基於requirejs.config()的config配置項來實現。要獲取這些信息的模塊可以加載特殊的依賴 ”moudle” ,並調用module.config().
首先我們可以還是試着做demo來理解下上面話的意思吧,我現在在項目requirejs下js/app文件下新建一個d.js. 然后在app.js初始化文件加入如下代碼:
requirejs.config({
config: {
'app/c': {
size: 'large'
},
'app/d': {
color: 'blue'
}
}
});
require(['app/c'],function(c){
console.log(c);
});
require(['app/d'],function(dss){
console.log(d);
});
在c.js里面這樣寫代碼:
define(function (require, exports, module) {
//其值是'large'
var size = module.config().size;
return size;
});
在控制台下運行可以看到能打印出 large值出來,這說明我們可以通過config配置項來給app/c.js傳遞一個模塊信息,比如如上面的一個對象{size:large},而在c.js里面直接可以通過module.config()方法來獲取size的值。
下面我們可以使用一個依賴數組來做同樣的事情,如下d.js代碼:
define(['module'], function (module) {
//Will be the value 'blue'
var color = module.config().color;
return color;
});
在控制台看 也一樣可以打印出color值出來。
6. 內部機制:
RequireJS加載的每個模塊作為script Tag,使用head.appendChild()方法。
在模塊的定義時,requireJS等到所有的依賴都加載完畢,會為函數的調用計算出正確的順序,然后在函數中通過正確的順序進行調用。
7. requireJS函數增加了第三個參數errbacks
還是做demo來演示下,我們還是在入口文件app.js下增加代碼,如下:
本來加載b模塊是app/b 但是我故意寫錯成b 所以就不會執行第一個回調函數,轉而到第二個回調函數內。如下彈框:
8.在模塊載入失敗回調中可以使用undef函數移除模塊的注冊。
如下代碼:
代碼:
require(['b'], function ($) {
//Do something with $ here
}, function (err) {
var failedId = err.requireModules && err.requireModules[0];
if (failedId === 'b') {
requirejs.undef(failedId);
}
});