requireJS(版本是2.1.15)學習教程(一)

龍恩0707發表於2014-11-01

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

         }

   });

相關文章