AMD/RequireJS 使用入門

參考資料

RequireJS 中文網
Javascript模塊化編程(三):require.js的用法——阮一峰

前言

本人菜鳥,入IT只為當鼓勵師。本編文章意在總結 RequireJS 的目的和用法。

一、require.js 的 目的

1.1 管理模塊間的依賴性,便于代碼的編寫和維護

RequireJS 鼓勵代碼的模塊化,主要目的是為了代碼的模塊化。

  • 如果一個文件需要依賴另外一些文件中定義的東西時,這個文件依賴的所有文件都要在它之前導入。過于復雜的系統,依賴關系可能出現相互交叉的情況,依賴關系的管理就更加難了。
    例如:51行到60行,分別是編寫的10個模塊。第61行引入的 main.js 是主模塊,編寫的是程序運行的過程。main.js 這個文件用到了從51行到60行的模塊,而它并沒有被其他模塊使用,故可以且必須放在最末尾導入。而上面的10個模塊,要確保模塊的依賴在該模塊導入之前就要導入,為了解除導入順序的限制,只能讓各模塊間解耦。


  • 而 RequireJS 使用了不同于傳統 <script>標簽 的腳本加載步驟。后續我們會看到 其引入js文件的方式是怎樣的。

1.2 實現js文件的異步加載,避免網頁失去響應

使用了 <script>標簽 這種傳統的引入js文件的方式,在加載js文件的時候,瀏覽器會停止網頁渲染,加載文件越多,網頁失去響應的時間就會越長。

若讀者對js模塊化還不甚理解,可先閱讀本小姐寫的另一篇文章 淺談JavaScript 模塊化

二、下載RequireJS

2.1 鏈接下載

require.js 2.1.11
require.js 2.1.11 壓縮版
r.js 2.1.11
r.js 可以讓你進行優化并能夠在 Node, Rhino 或者 xpcshell 中運行。

2.2 npm下載

若當前目錄下沒有找到 package.json 文件,則輸入 package init ,輸入完后設置好一些參數,即可生成 package.json 文件。


輸入 npm install requirejs --save-dev,下載完成后,可以在
package.json 文件中找到如下這行依賴。


在當前目錄下找到 node_modules\requirejs 路徑,即可找到 require.js 文件,找到 node_modules\requirejs\bin 路徑,即可找到 r.js 文件。



三、require.js 的加載

下載完 require.js 文件后,為了方便,我把文件復制到了 ./scripts/libs 目錄中(. 表示當前目錄,相對于 index.html 文件)。

RequireJS以一個相對于 baseUrl 的地址來加載所有的代碼。如果沒有顯式指定 configdata-main,則默認的 baseUrl 為包含 RequireJS 的那個 HTML 頁面的所屬目錄。

3.1 引入 require.js 文件

<script src="scripts/libs/require.js"></script>

加載這個文件,因為瀏覽器是同步加載的,也可能會造成網頁失去響應。
那么,你可以把它放在網頁底部加載:


或者,你可以將它寫成:

<script src="scripts/libs/require.js" defer asyn="true"></script>

async屬性表示該文件需異步加載,避免網頁失去響應。但IE瀏覽器不支持async,只支持defer,所以把defer也寫上。

3.2 引入網頁程序的主模塊

data-main 入口點

頁面頂層 <script> 標簽含有一個特殊的屬性 data-main,require.js使用它來啟動腳本加載過程,即指定網頁程序的主模塊。主模塊文件的命名一般可為 main.js。我們把 script 目錄下的 main.js 引入:


RequireJS默認假定所有的依賴資源都是js腳本,故可把main.js簡寫成main

注意:你在main.js中所設置的腳本是異步加載的。所以如果你在頁面中配置了其它JS加載,則不能保證它們所依賴的JS已經加載成功。例如:

<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
/**
* 由于 main.js 中的 foo 模塊是異步加載的,
* other.js 可能在 foo 模塊加載完之前就已經加載執行,
* 此時 other.js 中的 foo 模塊 指的是 scripts/foo.js 而非 libs/foo-1.1.3。
*/
require( ['foo'], function( foo ) {

});

四、主模塊的寫法

main.js "主模塊",是整個網頁的入口代碼。它有點像 C語言 的 main() 函數,所有代碼都從這里開始運行。

  • main.js 若不依賴其他模塊,則可以直接寫js代碼:
    // main.js
    console.log("加載成功!");
    這就相當于 C語言 中,直接把代碼寫在 main() 函數中:
    void main(int arg[], char* agvs[]) {
    printf("加載成功!");
    }
  • 但正常情況下,主模塊會依賴于其他模塊,這時就要使用AMD規范定義的 require() 函數。
    // main.js
    require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
    // 運行代碼
    });
    /**
    * dependency_array:依賴數組
    * callback_func:回調函數
    * require(dependency_array, callback_func);
    /
    require() 函數接收兩個參數:
    第一個參數是一個數組,表示所依賴的模塊有哪些;
    第二個參數是一個回調函數*,當前面指定的模塊都加載成功后,它將被調用。加載的模塊會以參數形式傳入該函數,從而在回調函數內部就可以使用這些模塊。
    require() 異步加載 moduleA、moduleB 和 moduleC,瀏覽器不會失去響應;它指定的回調函數,只有前面的模塊都加載成功后,才會運行,解決了依賴性的問題。

五、模塊的加載

使用 require.config() 方法,我們可以對模塊的加載行為進行自定義。require.config() 方法接收一個參數,該參數為包含一些指定屬性的原生對象,我們可通過設置對應屬性的值來修改加載行為。這些屬性有:baseUrlpathshimmapconfig 等等。

5.1 設置 path(module ID)

RequireJS 鼓勵在使用腳本時 以 module ID 替代 URL 地址,默認假定所有的依賴資源都是 js 腳本,因此無需在 module ID上再加 ".js" 后綴。
我們可以設置 require.config() 方法 中 傳入對象 的 path 屬性,來設置各模塊的 module ID。

requirejs.config({
    paths: {
        Bird: 'scripts/views/Bird',
        Block: 'scripts/views/Block',
        Counter: 'scripts/views/Counter',
        GameBg: 'scripts/views/GameBg',
        GameOver: 'scripts/views/GameOver',
        GrassLand: 'scripts/views/GrassLand',
        StartBtn: 'scripts/views/StartBtn',
        StartInfo: 'scripts/views/StartInfo',
        RandomCreator: 'scripts/utils/RandomCreator'
    }
});

// 加載 scripts/views/Bird.js 和 scripts/utils/randomCreator.js
require(['Bird', 'randomCreator'], function (Bird, randomCreator) {
    // 代碼
});

5.2 設置 baseUrl

baseUrl 可通過 requirejs.config() 手動設置。如果沒有顯式指定 config
data-main,則默認的 baseUrl 為包含 RequireJS 的那個 HTML 頁面的所屬目錄。設置以后,加載模塊時,路徑都會被解析為 baseUrl + path。若想避開該解析過程,設置 path 時可以:

  • 以 ".js" 結束;
  • 以 "/" 開始;
  • 包含 URL 協議, 如 "http:" or "https:"。
requirejs.config({
    baseUrl: "scripts/views",
    paths: {
        Bird: 'scripts/views/Bird',
        Block: 'scripts/views/Block',
        Counter: 'scripts/views/Counter',
        GameBg: 'scripts/views/GameBg',
        GameOver: 'scripts/views/GameOver',
        GrassLand: 'scripts/views/GrassLand',
        StartBtn: 'scripts/views/StartBtn',
        StartInfo: 'scripts/views/StartInfo',
        RandomCreator: '../utils/RandomCreator'
    }
});

// 加載 scripts/views/Bird.js 和 scripts/utils/randomCreator.js
require(['Bird', 'RandomCreator'], function (Bird, randomCreator) {
    // 代碼
});

如果某個模塊在另一臺主機上,也可直接指定其網址:

require.config({
    paths: {
        "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min"
    }
});

require.js 要求,每個模塊是一個單獨的 js 文件。如果加載多個模塊,就會發出多次HTTP請求,影響網頁的加載速度。因此,require.js 提供了一個 優化工具,當模塊部署完畢后,可用該工具將多個模塊合并成一個文件,減少HTTP請求數。

5.3 設置 shim

理論上,require.js 加載的模塊,必須是按照AMD規范、用 define() 函數定義的模塊。但是實際上,雖然已經有一部分流行的函數庫(比如jQuery)符合AMD規范,更多的庫并不符合。
為了能夠加載非規范的模塊,可設置 require.config() 方法 傳入對象 中的 shim 屬性。shim屬性的值是一個對象,這個對象里包含一些模板對象,而這些模板對象有兩個屬性:
① exports值(輸出的變量名):表明這個模塊外部調用時的名稱;
② deps數組:表明該模塊的依賴什么模塊。

require.config({
    shim: {
        // underscore 模塊,外部調用時使用 _ 指代該模塊
        'underscore': {
            exports: '_'
        },
        // backbone模塊,外部調用時使用 Backbone 指代該模塊
        // 這個模塊依賴于 underscore , jquery 模塊
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        }
    }
});

5.4 設置 map

對于給定的模塊前綴,使用一個不同的模塊ID來加載該模塊。

requirejs.config({
    map: {
       'some/newmodule': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});


另外在map中支持 *,意思是 "對于所有的模塊加載,使用本map配置"。如果還有更細化的map配置,會優先于 * 配置。

requirejs.config({
    map: {
        '*': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});

意思是: 除了 some/oldmodule 外的所有模塊,當要用 foo 時,使用 foo1.2 來替代。對于 some/oldmodule 自己,則使用 foo1.0

5.5 設置 config

若想設置一些配置信息(變量,方法)供對應模塊使用,可以設置 require.config() 方法 傳入對象 中的 config 屬性。要獲取這些信息的模塊可以加載特殊的依賴 module,并調用 module.config()。如:

// main.js
requirejs.config({
    config: {
        'bar': {
            size: 'large'
        },
        'baz': {
            color: 'blue'
        }
    }
});

// bar.js
define( function (require, exports, module) {
    var size = module.config().size;
    console.log(size);  // large
});

// baz.js
define(['module'], function (module) {
    var color = module.config().color;
    console.log(color);  // blue
});

六、定義模塊

模塊不同于傳統的腳本文件,它良好地定義了一個作用域來避免全局名稱空間污染。它可以顯式地列出其依賴關系,并以函數(定義此模塊的那個函數)參數的形式將這些依賴進行注入,而無需引用全局變量。

RequireJS 定義模塊采用AMD規范,使用 define() 方法。
假定現在有一個GameBg.js文件,它定義了一個 GameBg 模塊:

// GameBg.js
define( function () {
  
    var GameBg = {};

    return GameBg;
});

一個磁盤文件應該只定義 1 個模塊。多個模塊可以使用內置優化工具將其組織打包。

6.1 簡單的值對

如果一個模塊僅含值對,沒有任何依賴,可在 define() 中直接定義這些值對:

define({
    color: "black",
    size: "unisize"
});

6.2 函數式定義

如果一個模塊沒有任何依賴,但需要一個做初始化或配置工作的函數,則在 define() 中定義該函數,并將其傳給 define()

// GameBg.js
define( function () {
    
    // 一些初始化或配置工作
    // ...

    // 界面背景單例對象
    var GameBg = (function () {
        var _element = document.querySelector('#game-bg'),
            _width = document.querySelector('#game-bg').offsetWidth,
            _height = document.querySelector('#game-bg').offsetHeight;

        return {
            getElement : function () {
                return _element;
            },
            getWidth : function () {
                return _width;
            },
            getHeight : function () {
                return _height;
            },
            // ...
        }
    })();
    return GameBg;
});

6.3 存在依賴的函數式定義

模塊函數以參數 GameBgGrassLand 使用這兩個以 ./scripts/views/GameBg./scripts/views/GrassLand 名稱指定的模塊。在這兩個模塊加載完畢之前,模塊函數不會被調用。RequireJS 不鼓勵模塊定義全局變量,返回的 object 定義了 Bird 模塊。這種定義模式下,Bird 不作為一個全局變量而存在。

// Bird.js
define(['GameBg', 'GrassLand'], function (GameBg, GrassLand) {

    // Bird對象的構造函數
    var Bird = function (idName) {

       // ...
    };
    return Bird;
});

6.4 將模塊定義為一個函數

模塊的返回值類型 不一定是 一個對象,也可以是 一個函數。此處是一個返回了函數的模塊定義:

// Bird.js
define(['GameBg', 'GrassLand'], function (GameBg, GrassLand) {

    // Bird對象的構造函數
    return function (idName) {

       // ...
    };
});

6.5 簡單包裝CommonJS來定義模塊

define(function(require, exports, module) {
    var a = require('a'),
        b = require('b');

    // 返回模塊
    return function () {};
});

6.6 定義一個命名模塊

你可能會看到一些define()中包含了一個模塊名稱作為首個參數。

這些常由優化工具生成。你也可以自己顯式指定模塊名稱,但這使模塊更不具備移植性——就是說若你將文件移動到其他目錄下,你就得重命名。一般最好避免對模塊硬編碼,而是交給優化工具去生成。優化工具需要生成模塊名以將多個模塊打成一個包,加快到瀏覽器的載人速度。

define("FlappyBird",
    ["GameBg", "GrassLand"],
    function(GameBg, GrassLand) {
        //Define foo/title object in here.
   }
);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容