一:為什麼要使用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);
}
});