JS -- 一篇文章掌握RequireJS經常使用知識

本文采起按部就班的方式,從理論到實踐,從RequireJS官方API文檔中,總結出在使用RequireJS過程當中最經常使用的一些用法,並對文檔中不夠清晰具體的內容,加以例證和分析,分享給你們供你們參考,具體內容以下javascript

1. 模塊化
相信每一個前端開發人員在剛開始接觸js編程時,都寫過相似下面這樣風格的代碼:css

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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>
?
1
< a href = "javascript:;" onclick = "click(this);" title = "" >請點擊</ a >

這些代碼的特色是:html

  • 處處可見的全局變量
  • 大量的函數
  • 內嵌在html元素上的各類js調用

固然這些代碼自己在實現功能上並無錯誤,可是從代碼的可重用性,健壯性以及可維護性來講,這種編程方式是有問題的,尤爲是在頁面邏輯較爲複雜的應用中,這些問題會暴露地特別明顯:前端

  • 全局變量極易形成命名衝突
  • 函數式編程很是不利於代碼的組織和管理
  • 內嵌的js調用很不利於代碼的維護,由於html代碼有的時候是十分臃腫和龐大的

因此當這些問題出現的時候,js大牛們就開始尋找去解決這些問題的究極辦法,因而模塊化開發就出現了。正如模塊化這個概念的表面意思同樣,它要求在編寫代碼的時候,按層次,按功能,將獨立的邏輯,封裝成可重用的模塊,對外提供直接明瞭的調用接口,內部實現細節徹底私有,而且模塊之間的內部實如今執行期間互不干擾,最終的結果就是能夠解決前面舉例提到的問題。一個簡單遵循模塊化開發要求編寫的例子:html5

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//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);

以上代碼定義了三個模塊分別表示學生,課程和控制器,而後在main.js中調用了controller提供的add和list接口,爲lyzg這個學生添加了三門課程,而後在控制檯顯示了出來。運行結果以下:java

?
1
javascript;html;css

經過上例,能夠看出模塊化的代碼結構和邏輯十分清晰,代碼看起來十分優雅,另外因爲邏輯都經過模塊拆分,因此達到了解耦的目的,代碼的功能也會比較健壯。不過上例使用的這種模塊化開發方式也並非沒有問題,這個問題就是它仍是把模塊引用如student這些直接添加到了全局空間下,雖然經過模塊減小了不少全局空間的變量和函數,可是模塊引用自己仍是要依賴全局空間,才能被調用,當模塊較多,或者有引入第三方模塊庫時,仍然可能形成命名衝突的問題,因此這種全局空間下的模塊化開發的方式並非最完美的方式。目前常見的模塊化開發方式,全局空間方式是最基本的一種,另外常見的還有遵循AMD規範的開發方式,遵循CMD規範的開發方式,和ECMAScript 6的開發方式。須要說明的是,CMD和ES6跟本文的核心沒有關係,因此不會在此介紹,後面的內容主要介紹AMD以及實現了AMD規範的RequireJS。node

2. AMD規範
正如上文提到,實現模塊化開發的方式,另外常見的一種就是遵循AMD規範的實現方式,不過AMD規範並非具體的實現方式,而僅僅是模塊化開發的一種解決方案,你能夠把它理解成模塊化開發的一些接口聲明,若是你要實現一個遵循該規範的模塊化開發工具,就必須實現它預先定義的API。好比它要求在加載模塊時,必須使用以下的API調用方式:jquery

?
1
2
3
4
require([module], callback)
其中:
[module]:是一個數組,裏面的成員就是要加載的模塊;
callback:是模塊加載完成以後的回調函數

全部遵循AMD規範的模塊化工具,都必須按照它的要求去實現,好比RequireJS這個庫,就是徹底遵循AMD規範實現的,因此在利用RequireJS加載或者調用模塊時,若是你事先知道AMD規範的話,你就知道該怎麼用RequireJS了。規範的好處在於,不一樣的實現卻有相同的調用方式,很容易切換不一樣的工具使用,至於具體用哪個實現,這就跟各個工具的各自的優勢跟項目的特色有關係,這些都是在項目開始選型的時候須要肯定的。目前RequireJS不是惟一實現了AMD規範的庫,像Dojo這種更全面的js庫也都有AMD的實現。ajax

最後對AMD全稱作一個解釋,譯爲:異步模塊定義。異步強調的是,在加載模塊以及模塊所依賴的其它模塊時,都採用異步加載的方式,避免模塊加載阻塞了網頁的渲染進度。相比傳統的異步加載,AMD工具的異步加載更加簡便,並且還能實現按需加載,具體解釋在下一部分說明。編程

3. JavaScript的異步加載和按需加載
html中的script標籤在加載和執行過程當中會阻塞網頁的渲染,因此通常要求儘可能將script標籤放置在body元素的底部,以便加快頁面顯示的速度,還有一種方式就是經過異步加載的方式來加載js,這樣能夠避免js文件對html渲染的阻塞。

第1種異步加載的方式是直接利用腳本生成script標籤的方式:

?
1
2
3
4
5
6
7
8
( function () {
   var s = document.createElement( 'script' );
   s.type = 'text/javascript' ;
   s.async = true ;
   var x = document.getElementsByTagName( 'script' )[0];
   x.parentNode.insertBefore(s, x);
})();

這段代碼,放置在script標記內部,而後該script標記添加到body元素的底部便可。

第2種方式是藉助script的屬性:defer和async,defer這個屬性在IE瀏覽器和早起的火狐瀏覽器中支持,async在支持html5的瀏覽器上都支持,只要有這兩個屬性,script就會以異步的方式來加載,因此script在html中的位置就不重要了:

?
1
2
3
<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>

這種方式下,全部異步js在執行的時候仍是按順序執行的,否則就會存在依賴問題,好比若是上例中的main.js依賴foo.js和bar.js,可是main.js先執行的話就會出錯了。雖然歷來理論上這種方式也算不錯了,可是不夠好,由於它用起來很繁瑣,並且還有個問題就是頁面須要添加多個script標記以及沒有辦法徹底作到按需加載。

JS的按需加載分兩個層次,第一個層次是隻加載這個頁面可能被用到的JS,第二個層次是在只在用到某個JS的時候纔去加載。傳統地方式很容易作到第一個層次,可是不容易作到第二個層次,雖然咱們能夠經過合併和壓縮工具,將某個頁面全部的JS都添加到一個文件中去,最大程度減小資源請求量,可是這個JS請求到客戶端之後,其中有不少內容可能都用不上,要是有個工具可以作到在須要的時候纔去加載相關js就完美解決問題了,好比RequireJS。

4. RequireJS經常使用用法總結
前文屢次說起RequireJS,本部分將對它的經常使用用法詳細說明,它的官方地址是:http://www.requirejs.cn/,你能夠到該地址去下載最新版RequireJS文件。RequireJS做爲目前使用最普遍的AMD工具,它的主要優勢是:

  • 徹底支持模塊化開發
  • 能將非AMD規範的模塊引入到RequireJS中使用
  • 異步加載JS
  • 徹底按需加載依賴模塊,模塊文件只須要壓縮混淆,不須要合併
  • 錯誤調試
  • 插件支持

4.01 如何使用RequireJS
使用方式很簡單,只要一個script標記就能夠在網頁中加載RequireJS:

?
1
<script defer async= "true" src= "/bower_components/requirejs/require.js" ></script>

因爲這裏用到了defer和async這兩個異步加載的屬性,因此require.js是異步加載的,你把這個script標記放置在任何地方都沒有問題。

4.02 如何利用RequireJS加載並執行當前網頁的邏輯JS

4.01解決的僅僅是RequireJS的使用問題,但它僅僅是一個JS庫,是一個被當前頁面的邏輯所利用的工具,真正實現網頁功能邏輯的是咱們要利用RequireJS編寫的主JS,這個主JS(假設這些代碼都放置在main.js文件中)又該如何利用RJ來加載執行呢?方式以下:

?
1
<script data-main= "scripts/main.js" defer async= "true" src= "/bower_components/requirejs/require.js" ></script>

對比4.01,你會發現script標記多了一個data-main,RJ用這個配置當前頁面的主JS,你要把邏輯都寫在這個main.js裏面。當RJ自身加載執行後,就會再次異步加載main.js。這個main.js是當前網頁全部邏輯的入口,理想狀況下,整個網頁只須要這一個script標記,利用RJ加載依賴的其它文件,如jquery等。

 4.03 main.js怎麼寫
假設項目的目錄結構爲:

main.js是跟當前頁面相關的主JS,app文件夾存放本項目自定義的模塊,lib存放第三方庫。

html中按4.02的方式配置RJ。main.js的代碼以下:

?
1
2
3
require([ 'lib/foo' , 'app/bar' , 'app/app' ], function (foo, bar, app) {
   //use foo bar app do sth
});

在這段JS中,咱們利用RJ提供的require方法,加載了三個模塊,而後在這個三個模塊都加載成功以後執行頁面邏輯。require方法有2個參數,第一個參數是數組類型的,實際使用時,數組的每一個元素都是一個模塊的module ID,第二個參數是一個回調函數,這個函數在第一個參數定義的全部模塊都加載成功後回調,形參的個數和順序分別與第一個參數定義的模塊對應,好比第一個模塊時lib/foo,那麼這個回調函數的第一個參數就是foo這個模塊的引用,在回調函數中咱們使用這些形參來調用各個模塊的方法,因爲回調是在各模塊加載以後才調用的,因此這些模塊引用確定都是有效的。

從以上這個簡短的代碼,你應該已經知道該如何使用RJ了。

4.04 RJ的baseUrl和module ID
在介紹RJ如何去解析依賴的那些模塊JS的路徑時,必須先弄清楚baseUrl和module ID這兩個概念。

html中的base元素能夠定義當前頁面內部任何http請求的url前綴部分,RJ的baseUrl跟這個base元素起的做用是相似的,因爲RJ老是動態地請求依賴的JS文件,因此必然涉及到一個JS文件的路徑解析問題,RJ默認採用一種baseUrl + moduleID的解析方式,這個解析方式後續會舉例說明。這個baseUrl很是重要,RJ對它的處理遵循以下規則:

  • 在沒有使用data-main和config的狀況下,baseUrl默認爲當前頁面的目錄
  • 在有data-main的狀況下,main.js前面的部分就是baseUrl,好比上面的scripts/
  • 在有config的狀況下,baseUrl以config配置的爲準

上述三種方式,優先級由低到高排列。

data-main的使用方式,你已經知道了,config該如何配置,以下所示:

?
1
2
3
require.config({
   baseUrl: 'scripts'
});

這個配置必須放置在main.js的最前面。data-main與config配置同時存在的時候,以config爲準,因爲RJ的其它配置也是在這個位置配置的,因此4.03中的main.js能夠改爲以下結構,以便未來的擴展:

?
1
2
3
4
5
6
7
require.config({
   baseUrl: 'scripts'
});
 
require([ 'lib/foo' , 'app/bar' , 'app/app' ], function (foo, bar, app) {
   // use foo bar app do sth
});

關於module ID,就是在require方法以及後續的define方法裏,用在依賴數組這個參數裏,用來標識一個模塊的字符串。上面代碼中的['lib/foo', 'app/bar', 'app/app']就是一個依賴數組,其中的每一個元素都是一個module ID。值得注意的是,module ID並不必定是該module 相關JS路徑的一部分,有的module ID很短,但可能路徑很長,這跟RJ的解析規則有關。下一節詳細介紹。

4.05 RJ的文件解析規則
RJ默認按baseUrl + module ID的規則,解析文件,而且它默認要加載的文件都是js,因此你的module ID裏面能夠不包含.js的後綴,這就是爲啥你看到的module ID都是lib/foo, app/bar這種形式了。有三種module ID,不適用這種規則:

假如main.js以下使用:

?
1
2
3
4
5
6
7
require.config({
   baseUrl: 'scripts'
});
 
require([ '/lib/foo' , 'test.js' , '/js/jquery' ], function (foo, bar, app) {
   // use foo bar app do sth
});

這三個module 都不會根據baseUrl + module ID的規則來解析,而是直接用module ID來解析,等效於下面的代碼:

?
1
2
3
<script src= "/lib/foo.js" ></script>
<script src= "test.js" ></script>
<script src= "/js/jquery.js" ></script>

各類module ID解析舉例:

例1,項目結構以下:

main.js以下:

?
1
2
3
4
5
6
7
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

根據baseUrl + moduleID,以及自動補後綴.js,最終這三個module的js文件路徑爲:

?
1
2
3
scripts/lib/foo.js
scripts/app/bar.js
scripts/app/app.js

例2,項目結構同例1:

main.js改成:

?
1
2
3
4
5
6
7
8
9
10
11
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
    app: '../app'
   }
});
 
 
require([ 'foo' , 'app/bar' , 'app/app' ], function (foo, bar, app) {
   // use foo bar app do sth
});

這裏出現了一個新的配置paths,它的做用是針對module ID中特定的部分,進行轉義,如以上代碼中對app這個部分,轉義爲../app,這表示一個相對路徑,相對位置是baseUrl所指定的目錄,由項目結構可知,../app其實對應的是scirpt/app目錄。正由於有這個轉義的存在,因此以上代碼中的app/bar才能被正確解析,不然還按baseUrl + moduleID的規則,app/bar不是應該被解析成scripts/lib/app/bar.js嗎,但實際並不是如此,app/bar被解析成scripts/app/bar.js,其中起關鍵做用的就是paths的配置。經過這個舉例,能夠看出module ID並不必定是js文件路徑中的一部分,paths的配置對於路徑過程的js特別有效,由於能夠簡化它的module ID。

另外第一個模塊的ID爲foo,同時沒有paths的轉義,因此根據解析規則,它的文件路徑時:scripts/lib/foo.js。

paths的配置中只有當模塊位於baseUrl所指定的文件夾的同層目錄,或者更上層的目錄時,纔會用到../這種相對路徑。

例3,項目結果同例1,main.js同例2:

這裏要說明的問題稍微特殊,不以main.js爲例,而以app.js爲例,且app依賴bar,固然config仍是須要在main.js中定義的,因爲這個問題在定義模塊的時候更加常見,因此用define來舉例,假設app.js模塊以下定義:

?
1
2
3
4
5
6
7
define([ './bar' ], function (bar) {
    return {
      doSth: function () {
        bar.doSth();
      }
    }
});

上面的代碼經過define定義了一個模塊,這個define函數後面介紹如何定義模塊的時候再來介紹,這裏簡單瞭解。這裏這種用法的第一個參數跟require函數同樣,是一個依賴數組,第二個參數是一個回調,也是在全部依賴加載成功以後調用,這個回調的返回值會成爲這個模塊的引用被其它模塊所使用。

這裏要說的問題仍是跟解析規則相關的,若是徹底遵照RJ的解析規則,這裏的依賴應該配置成app/bar纔是正確的,但因爲app.js與bar.js位於同一個目錄,因此徹底可利用./這個同目錄的相對標識符來解析js,這樣的話只要app.js已經加載成功了,那麼去同目錄下找bar.js就確定能找到了。這種配置在定義模塊的時候很是有意義,這樣你的模塊就不依賴於放置這些模塊的文件夾名稱了。

4.06 RJ的異步加載
RJ無論是require方法仍是define方法的依賴模塊都是異步加載的,因此下面的代碼不必定能解析到正確的JS文件:

?
1
2
3
4
5
6
7
8
9
10
11
12
<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會比它先加載,可是RJ的配置存在於main.js裏面,因此在加載other.js讀不到RJ的配置,在other.js執行的時候解析出來的foo的路徑就會變成scripts/foo.js,而正確路徑應該是scripts/libs/foo-1.1.3.js。

儘管RJ的依賴是異步加載的,可是已加載的模塊在屢次依賴的時候,不會再從新加載:

?
1
2
3
4
5
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定義的時候,只用了一個形參,這主要是爲了減小形參的數量,避免整個回調的簽名很長。依賴的模塊在回調內部能夠直接用require(moduleID)的參數獲得,因爲在回調執行前,依賴的模塊已經加載,因此此處調用不會再從新加載。可是若是此處獲取一個並不在依賴數組中出現的module ID,require頗有可能獲取不到該模塊引用,由於它可能須要從新加載,若是它沒有在其它模塊中被加載過的話。

4.07 RJ官方推薦的JS文件組織結構
RJ建議,文件組織儘可能扁平,不要多層嵌套,最理想的是跟項目相關的放在一個文件夾,第三方庫放在一個文件夾,以下所示:

4.08 使用define定義模塊
AMD規定的模塊定義規範爲:

?
1
2
3
4
5
6
define(id?, dependencies?, factory);
 
其中:
id: 模塊標識,能夠省略。
dependencies: 所依賴的模塊,能夠省略。
factory: 模塊的實現,或者一個JavaScript對象

關於第一個參數,本文不會涉及,由於RJ建議全部模塊都不要使用第一個參數,若是使用第一個參數定義的模塊成爲命名模塊,不適用第一個參數的模塊成爲匿名模塊,命名模塊若是改名,全部依賴它的模塊都得修改!第二個參數是依賴數組,跟require同樣,若是沒有這個參數,那麼定義的就是一個無依賴的模塊;最後一個參數是回調或者是一個簡單對象,在模塊加載完畢後調用,固然沒有第二個參數,最後一個參數也會調用。

本部分所舉例都採用以下項目結構:

1. 定義簡單對象模塊:

app/bar.js

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
define({
  bar: 'I am bar.'
});
利用main.js測試:
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   }
});
 
require([ 'app/bar' ], function (bar) {
   console.log(bar); // {bar: 'I am bar.'}
});

2. 定義無依賴的模塊:

app/nodec.js:

?
1
2
3
4
5
define( function () {
   return {
     nodec: "yes, I don't need dependence."
   }
});

利用main.js測試:

?
1
2
3
4
5
6
7
8
9
10
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   }
});
 
require([ 'app/nodec' ], function (nodec) {
   console.log(nodec); // {nodec: yes, I don't need dependence.'}
});

3. 定義依賴其它模塊的模塊:

app/dec.js:

?
1
2
3
4
5
6
define([ 'jquery' ], function ($){
   //use $ do sth ...
   return {
     useJq: true
   }
});

利用main.js測試:

?
1
2
3
4
5
6
7
8
9
10
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   }
});
 
require([ 'app/dec' ], function (dec) {
   console.log(dec); //{useJq: true}
});

4. 循環依賴:
當一個模塊foo的依賴數組中存在bar,bar模塊的依賴數組中存在foo,就會造成循環依賴,稍微修改下bar.js和foo.js以下。

app/bar.js:

?
1
2
3
4
5
6
7
8
define([ 'foo' ], function (foo){
  return {
  name: 'bar' ,
  hi: function (){
   console.log( 'Hi! ' + foo.name);
  }
  }
});

lib/foo.js:

?
1
2
3
4
5
6
7
8
define([ 'app/bar' ], function (bar){
  return {
  name: 'foo' ,
  hi: function (){
   console.log( 'Hi! ' + bar.name);
  }
  }
});

利用main.js測試:

?
1
2
3
4
5
6
7
8
9
10
11
12
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   }
});
 
 
require([ 'app/bar' , 'foo' ], function (bar, foo) {
   bar.hi();
   foo.hi();
});

運行結果:

若是改變main.js中require部分的依賴順序,結果:

循環依賴致使兩個依賴的module之間,始終會有一個在獲取另外一個的時候,獲得undefined。解決方法是,在定義module的時候,若是用到循環依賴的時候,在define內部經過require從新獲取。main.js不變,bar.js改爲:

?
1
2
3
4
5
6
7
8
9
define([ 'require' , 'foo' ], function (require, foo) {
   return {
     name: 'bar' ,
     hi: function () {
      foo = require( 'foo' );
       console.log( 'Hi! ' + foo.name);
     }
   }
});

foo.js改爲:

?
1
2
3
4
5
6
7
8
9
define([ 'require' , 'app/bar' ], function (require, bar) {
   return {
     name: 'foo' ,
     hi: function () {
      bar = require( 'app/bar' );
       console.log( 'Hi! ' + bar.name);
     }
   }
});

利用上述代碼,從新執行,結果是:

模塊定義總結:無論模塊是用回調函數定義仍是簡單對象定義,這個模塊輸出的是一個引用,因此這個引用必須是有效的,你的回調不能返回undefined,可是不侷限於對象類型,還能夠是數組,函數,甚至是基本類型,只不過若是返回對象,你能經過這個對象組織更多的接口。

4.09 內置的RJ模塊
再看看這個代碼:

?
1
2
3
4
5
6
7
8
9
define([ 'require' , 'app/bar' ], function (require) {
   return {
     name: 'foo' ,
     hi: function () {
       var bar = require( 'app/bar' );
       console.log( 'Hi! ' + bar.name);
     }
   }
});

依賴數組中的require這個moduleID對應的是一個內置模塊,利用它加載模塊,怎麼用你已經看到了,好比在main.js中,在define中。另一個內置模塊是module,這個模塊跟RJ的另一個配置有關,具體用法請在第5大部分去了解。

4.10 其它RJ有用功能
1. 生成相對於模塊的URL地址

?
1
2
3
define([ "require" ], function (require) {
   var cssUrl = require.toUrl( "./style.css" );
});

這個功能在你想要動態地加載一些文件的時候有用,注意要使用相對路徑。

2. 控制檯調試

require("module/name").callSomeFunction()
假如你想在控制檯中查看某個模塊都有哪些方法能夠調用,若是這個模塊已經在頁面加載的時候經過依賴被加載事後,那麼就能夠用以上代碼在控制檯中作各類測試了。

5. RequireJS經常使用配置總結
在RJ的配置中,前面已經接觸到了baseUrl,paths,另外幾個經常使用的配置是:

  • shim
  • config
  • enforceDefine
  • urlArgs

5.01 shim
爲那些沒有使用define()來聲明依賴關係、設置模塊的"瀏覽器全局變量注入"型腳本作依賴和導出配置。

例1:利用exports將模塊的全局變量引用與RequireJS關聯

main.js以下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   },
   shim: {
    underscore: {
    exports: '_'
    }
   }
});
 
require([ 'underscore' ], function (_) {
   // 如今能夠經過_調用underscore的api了
});

如你所見,RJ在shim中添加了一個對underscore這個模塊的配置,並經過exports屬性指定該模塊暴露的全局變量,以便RJ可以對這些模塊統一管理。

例2:利用deps配置js模塊的依賴

main.js以下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require.config({
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   },
   shim: {
    backbone: {
     deps: [ 'underscore' , 'jquery' ],
     exports: 'Backbone'
    }
   }
});
 
require([ 'backbone' ], function (Backbone) {
   //use Backbone's API
});

因爲backbone這個組件依賴jquery和underscore,因此能夠經過deps屬性配置它的依賴,這樣backbone將會在另外兩個模塊加載完畢以後纔會加載。

例3:jquery等庫插件配置方法

代碼舉例以下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
經常須要將配置信息傳給一個模塊。這些配置每每是application級別的信息,須要一個手段將它們向下傳遞給模塊。在RequireJS中,基於requirejs.config()的config配置項來實現。要獲取這些信息的模塊能夠加載特殊的依賴「module」,並調用module.config()。

例1:在requirejs.config()中定義config,以供其它模塊使用

?
1
2
3
4
5
6
7
8
9
10
requirejs.config({
   config: {
     'bar' : {
       size: 'large'
     },
     'baz' : {
       color: 'blue'
     }
   }
});

如你所見,config屬性中的bar這一節是在用於module ID爲bar這個模塊的,baz這一節是用於module ID爲baz這個模塊的。具體使用以bar.js舉例:

?
1
2
3
define([ 'module' ], function (module) {
   //Will be the value 'large'var size = module.config().size;
});

前面提到過,RJ的內置模塊除了require還有一個module,用法就在此處,經過它能夠來加載config的內容。

5.03 enforceDefine
若是設置爲true,則當一個腳本不是經過define()定義且不具有可供檢查的shim導出字串值時,就會拋出錯誤。這個屬性能夠強制要求全部RJ依賴或加載的模塊都要經過define或者shim被RJ來管理,同時它還有一個好處就是用於錯誤檢測。

5.04 urlArgs
RequireJS獲取資源時附加在URL後面的額外的query參數。做爲瀏覽器或服務器未正確配置時的「cache bust」手段頗有用。使用cache bust配置的一個示例:

urlArgs: "bust=" + (new Date()).getTime()
6. 錯誤處理
6.01 加載錯誤的捕獲
IE中捕獲加載錯誤不完美:

IE 6-8中的script.onerror無效。沒有辦法判斷是否加載一個腳本會致使404錯;更甚地,在404中依然會觸發state爲complete的onreadystatechange事件。
IE 9+中script.onerror有效,但有一個bug:在執行腳本以後它並不觸發script.onload事件句柄。所以它沒法支持匿名AMD模塊的標準方法。因此script.onreadystatechange事件仍被使用。可是,state爲complete的onreadystatechange事件會在script.onerror函數觸發以前觸發。
因此爲了支持在IE中捕獲加載錯誤,須要配置enforceDefine爲true,這不得不要求你全部的模塊都用define定義,或者用shim配置RJ對它的引用。

注意:若是你設置了enforceDefine: true,並且你使用data-main=""來加載你的主JS模塊,則該主JS模塊必須調用define()而不是require()來加載其所需的代碼。主JS模塊仍然可調用require/requirejs來設置config值,但對於模塊加載必須使用define()。好比原來的這段就會報錯:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require.config({
  enforceDefine: true ,
   baseUrl: 'scripts/lib' ,
   paths: {
     app: '../app'
   },
   shim: {
    backbone: {
    deps: [ 'underscore' , 'jquery' ],
       exports: 'Backbone'
    }
   }
});
require([ 'backbone' ], function (Backbone) {
   console.log(Backbone);
});

把最後三行改爲:

?
1
2
3
define([ 'backbone' ], function (Backbone) {
   console.log(Backbone);
});

纔不會報錯。

6.02 paths備錯

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
requirejs.config({
   //To get timely, correct error triggers in IE, force a define/shim exports check.
   enforceDefine: true ,
   paths: {
     jquery: [
       //If the CDN location fails, load from this location
       'lib/jquery'
     ]
   }
});
 
//Later
require([ 'jquery' ], function ($) {
});

上述代碼先嚐試加載CDN版本,若是出錯,則退回到本地的lib/jquery.js。

注意: paths備錯僅在模塊ID精確匹配時工做。這不一樣於常規的paths配置,常規配置可匹配模塊ID的任意前綴部分。備錯主要用於很是的錯誤恢復,而不是常規的path查找解析,由於那在瀏覽器中是低效的。

6.03 全局 requirejs.onError
爲了捕獲在局域的errback中未捕獲的異常,你能夠重載requirejs.onError():

?
1
2
3
4
5
6
7
8
requirejs.onError = function (err) {
   console.log(err.requireType);
   if (err.requireType === 'timeout' ) {
     console.log( 'modules: ' + err.requireModules);
   }
 
   throw err;
};
相關文章
相關標籤/搜索