參考資料
前言
本人菜鳥熊户,入IT只為當(dāng)鼓勵(lì)師陆赋。本編文章意在總結(jié) RequireJS 的目的和用法。
一蒙揣、require.js 的 目的
1.1 管理模塊間的依賴性共屈,便于代碼的編寫和維護(hù)
RequireJS 鼓勵(lì)代碼的模塊化柬采,主要目的是為了代碼的模塊化。
-
如果一個(gè)文件需要依賴另外一些文件中定義的東西時(shí)搏予,這個(gè)文件依賴的所有文件都要在它之前導(dǎo)入熊锭。過(guò)于復(fù)雜的系統(tǒng),依賴關(guān)系可能出現(xiàn)相互交叉的情況,依賴關(guān)系的管理就更加難了碗殷。
例如:51行到60行精绎,分別是編寫的10個(gè)模塊。第61行引入的 main.js 是主模塊亿扁,編寫的是程序運(yùn)行的過(guò)程捺典。main.js 這個(gè)文件用到了從51行到60行的模塊,而它并沒(méi)有被其他模塊使用从祝,故可以且必須放在最末尾導(dǎo)入襟己。而上面的10個(gè)模塊,要確保模塊的依賴在該模塊導(dǎo)入之前就要導(dǎo)入牍陌,為了解除導(dǎo)入順序的限制,只能讓各模塊間解耦毒涧。
- 而 RequireJS 使用了不同于傳統(tǒng)
<script>
標(biāo)簽 的腳本加載步驟契讲。后續(xù)我們會(huì)看到 其引入js文件的方式是怎樣的仿吞。
1.2 實(shí)現(xiàn)js文件的異步加載,避免網(wǎng)頁(yè)失去響應(yīng)
使用了 <script>
標(biāo)簽 這種傳統(tǒng)的引入js文件的方式捡偏,在加載js文件的時(shí)候,瀏覽器會(huì)停止網(wǎng)頁(yè)渲染你虹,加載文件越多,網(wǎng)頁(yè)失去響應(yīng)的時(shí)間就會(huì)越長(zhǎng)彤避。
若讀者對(duì)js模塊化還不甚理解傅物,可先閱讀本小姐寫的另一篇文章 淺談JavaScript 模塊化琉预。
二、下載RequireJS
2.1 鏈接下載
require.js 2.1.11
require.js 2.1.11 壓縮版
r.js 2.1.11
r.js 可以讓你進(jìn)行優(yōu)化并能夠在 Node, Rhino 或者 xpcshell 中運(yùn)行模孩。
2.2 npm下載
若當(dāng)前目錄下沒(méi)有找到 package.json 文件,則輸入 package init
介却,輸入完后設(shè)置好一些參數(shù)块茁,即可生成 package.json 文件桂肌。
輸入 npm install requirejs --save-dev
永淌,下載完成后,可以在
package.json 文件中找到如下這行依賴谭跨。
在當(dāng)前目錄下找到 node_modules\requirejs 路徑李滴,即可找到 require.js 文件,找到 node_modules\requirejs\bin 路徑谆扎,即可找到 r.js 文件芹助。
三、require.js 的加載
下載完 require.js 文件后无蜂,為了方便蒙谓,我把文件復(fù)制到了 ./scripts/libs
目錄中(. 表示當(dāng)前目錄,相對(duì)于 index.html 文件)彼乌。
RequireJS以一個(gè)相對(duì)于 baseUrl 的地址來(lái)加載所有的代碼渊迁。如果沒(méi)有顯式指定 config
及 data-main
,則默認(rèn)的 baseUrl 為包含 RequireJS 的那個(gè) HTML 頁(yè)面的所屬目錄毒租。
3.1 引入 require.js 文件
<script src="scripts/libs/require.js"></script>
加載這個(gè)文件箱叁,因?yàn)闉g覽器是同步加載的,也可能會(huì)造成網(wǎng)頁(yè)失去響應(yīng)算色。
那么螟够,你可以把它放在網(wǎng)頁(yè)底部加載:
或者峡钓,你可以將它寫成:
<script src="scripts/libs/require.js" defer asyn="true"></script>
async屬性表示該文件需異步加載若河,避免網(wǎng)頁(yè)失去響應(yīng)。但I(xiàn)E瀏覽器不支持async萧福,只支持defer,所以把defer也寫上膏燕。
3.2 引入網(wǎng)頁(yè)程序的主模塊
data-main 入口點(diǎn)
頁(yè)面頂層 <script>
標(biāo)簽含有一個(gè)特殊的屬性 data-main
饲窿,require.js使用它來(lái)啟動(dòng)腳本加載過(guò)程,即指定網(wǎng)頁(yè)程序的主模塊阀溶。主模塊文件的命名一般可為 main.js鸦泳。我們把 script 目錄下的 main.js 引入:
注意:你在main.js中所設(shè)置的腳本是異步加載的击纬。所以如果你在頁(yè)面中配置了其它JS加載钾麸,則不能保證它們所依賴的JS已經(jīng)加載成功。例如:
<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 模塊加載完之前就已經(jīng)加載執(zhí)行,
* 此時(shí) other.js 中的 foo 模塊 指的是 scripts/foo.js 而非 libs/foo-1.1.3实撒。
*/
require( ['foo'], function( foo ) {
});
四涉瘾、主模塊的寫法
main.js "主模塊",是整個(gè)網(wǎng)頁(yè)的入口代碼立叛。它有點(diǎn)像 C語(yǔ)言 的 main() 函數(shù),所有代碼都從這里開始運(yùn)行原在。
- main.js 若不依賴其他模塊,則可以直接寫js代碼:
// main.js
console.log("加載成功庶柿!");
這就相當(dāng)于 C語(yǔ)言 中,直接把代碼寫在 main() 函數(shù)中:
void main(int arg[], char* agvs[]) {
printf("加載成功甚负!");
} - 但正常情況下审残,主模塊會(huì)依賴于其他模塊,這時(shí)就要使用AMD規(guī)范定義的 require() 函數(shù)搅轿。
// main.js
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// 運(yùn)行代碼
});
/**
* dependency_array:依賴數(shù)組
* callback_func:回調(diào)函數(shù)
* require(dependency_array, callback_func);
/
require() 函數(shù)接收兩個(gè)參數(shù):
①第一個(gè)參數(shù)是一個(gè)數(shù)組,表示所依賴的模塊有哪些既穆;
②第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù)*雀鹃,當(dāng)前面指定的模塊都加載成功后,它將被調(diào)用囊颅。加載的模塊會(huì)以參數(shù)形式傳入該函數(shù)傅瞻,從而在回調(diào)函數(shù)內(nèi)部就可以使用這些模塊。
require() 異步加載 moduleA嗅骄、moduleB 和 moduleC,瀏覽器不會(huì)失去響應(yīng);它指定的回調(diào)函數(shù)宏多,只有前面的模塊都加載成功后,才會(huì)運(yùn)行肾请,解決了依賴性的問(wèn)題更胖。
五隔显、模塊的加載
使用 require.config()
方法饵逐,我們可以對(duì)模塊的加載行為進(jìn)行自定義。require.config()
方法接收一個(gè)參數(shù)倍权,該參數(shù)為包含一些指定屬性的原生對(duì)象,我們可通過(guò)設(shè)置對(duì)應(yīng)屬性的值來(lái)修改加載行為当船。這些屬性有:baseUrl
默辨、path
、shim
缩幸、map
、config
等等蒸绩。
5.1 設(shè)置 path(module ID)
RequireJS 鼓勵(lì)在使用腳本時(shí) 以 module ID 替代 URL 地址铃肯,默認(rèn)假定所有的依賴資源都是 js 腳本,因此無(wú)需在 module ID上再加 ".js" 后綴押逼。
我們可以設(shè)置 require.config()
方法 中 傳入對(duì)象 的 path
屬性,來(lái)設(shè)置各模塊的 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 設(shè)置 baseUrl
baseUrl 可通過(guò) requirejs.config()
手動(dòng)設(shè)置漂彤。如果沒(méi)有顯式指定 config
及 data-main
,則默認(rèn)的 baseUrl
為包含 RequireJS 的那個(gè) HTML 頁(yè)面的所屬目錄立润。設(shè)置以后媳板,加載模塊時(shí),路徑都會(huì)被解析為 baseUrl + path
蛉幸。若想避開該解析過(guò)程丛晦,設(shè)置 path
時(shí)可以:
- 以 ".js" 結(jié)束提陶;
- 以 "/" 開始;
- 包含 URL 協(xié)議, 如 "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) {
// 代碼
});
如果某個(gè)模塊在另一臺(tái)主機(jī)上仲器,也可直接指定其網(wǎng)址:
require.config({
paths: {
"jquery": "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min"
}
});
require.js 要求,每個(gè)模塊是一個(gè)單獨(dú)的 js 文件蝶糯。如果加載多個(gè)模塊辆沦,就會(huì)發(fā)出多次HTTP請(qǐng)求,影響網(wǎng)頁(yè)的加載速度肢扯。因此,require.js 提供了一個(gè) 優(yōu)化工具乍钻,當(dāng)模塊部署完畢后铭腕,可用該工具將多個(gè)模塊合并成一個(gè)文件,減少HTTP請(qǐng)求數(shù)累舷。
5.3 設(shè)置 shim
理論上,require.js 加載的模塊析孽,必須是按照AMD規(guī)范只怎、用 define()
函數(shù)定義的模塊。但是實(shí)際上尝盼,雖然已經(jīng)有一部分流行的函數(shù)庫(kù)(比如jQuery)符合AMD規(guī)范盾沫,更多的庫(kù)并不符合。
為了能夠加載非規(guī)范的模塊赴精,可設(shè)置 require.config()
方法 傳入對(duì)象 中的 shim
屬性。shim
屬性的值是一個(gè)對(duì)象一忱,這個(gè)對(duì)象里包含一些模板對(duì)象谭确,而這些模板對(duì)象有兩個(gè)屬性:
① exports值(輸出的變量名):表明這個(gè)模塊外部調(diào)用時(shí)的名稱;
② deps數(shù)組:表明該模塊的依賴什么模塊逐哈。
require.config({
shim: {
// underscore 模塊,外部調(diào)用時(shí)使用 _ 指代該模塊
'underscore': {
exports: '_'
},
// backbone模塊禀梳,外部調(diào)用時(shí)使用 Backbone 指代該模塊
// 這個(gè)模塊依賴于 underscore , jquery 模塊
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
}
});
5.4 設(shè)置 map
對(duì)于給定的模塊前綴肠骆,使用一個(gè)不同的模塊ID來(lái)加載該模塊。
requirejs.config({
map: {
'some/newmodule': {
'foo': 'foo1.2'
},
'some/oldmodule': {
'foo': 'foo1.0'
}
}
});
另外在map中支持
*
嘴瓤,意思是 "對(duì)于所有的模塊加載唯咬,使用本map配置"。如果還有更細(xì)化的map配置胆胰,會(huì)優(yōu)先于 *
配置蜀涨。
requirejs.config({
map: {
'*': {
'foo': 'foo1.2'
},
'some/oldmodule': {
'foo': 'foo1.0'
}
}
});
意思是: 除了 some/oldmodule
外的所有模塊,當(dāng)要用 foo
時(shí)厚柳,使用 foo1.2
來(lái)替代。對(duì)于 some/oldmodule
自己便监,則使用 foo1.0
。
5.5 設(shè)置 config
若想設(shè)置一些配置信息(變量烧董,方法)供對(duì)應(yīng)模塊使用,可以設(shè)置 require.config()
方法 傳入對(duì)象 中的 config
屬性预吆。要獲取這些信息的模塊可以加載特殊的依賴 module
胳泉,并調(diào)用 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
});
六凤瘦、定義模塊
模塊不同于傳統(tǒng)的腳本文件钳吟,它良好地定義了一個(gè)作用域來(lái)避免全局名稱空間污染。它可以顯式地列出其依賴關(guān)系红且,并以函數(shù)(定義此模塊的那個(gè)函數(shù))參數(shù)的形式將這些依賴進(jìn)行注入,而無(wú)需引用全局變量嗤放。
RequireJS 定義模塊采用AMD規(guī)范壁酬,使用 define()
方法。
假定現(xiàn)在有一個(gè)GameBg.js文件舆乔,它定義了一個(gè) GameBg 模塊:
// GameBg.js
define( function () {
var GameBg = {};
return GameBg;
});
一個(gè)磁盤文件應(yīng)該只定義 1 個(gè)模塊希俩。多個(gè)模塊可以使用內(nèi)置優(yōu)化工具將其組織打包。
6.1 簡(jiǎn)單的值對(duì)
如果一個(gè)模塊僅含值對(duì)颜武,沒(méi)有任何依賴,可在 define()
中直接定義這些值對(duì):
define({
color: "black",
size: "unisize"
});
6.2 函數(shù)式定義
如果一個(gè)模塊沒(méi)有任何依賴这吻,但需要一個(gè)做初始化或配置工作的函數(shù)篙议,則在 define()
中定義該函數(shù),并將其傳給 define()
:
// GameBg.js
define( function () {
// 一些初始化或配置工作
// ...
// 界面背景單例對(duì)象
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 存在依賴的函數(shù)式定義
模塊函數(shù)以參數(shù) GameBg
及 GrassLand
使用這兩個(gè)以 ./scripts/views/GameBg
及 ./scripts/views/GrassLand
名稱指定的模塊移怯。在這兩個(gè)模塊加載完畢之前,模塊函數(shù)不會(huì)被調(diào)用芋酌。RequireJS 不鼓勵(lì)模塊定義全局變量雁佳,返回的 object 定義了 Bird
模塊。這種定義模式下堵腹,Bird
不作為一個(gè)全局變量而存在星澳。
// Bird.js
define(['GameBg', 'GrassLand'], function (GameBg, GrassLand) {
// Bird對(duì)象的構(gòu)造函數(shù)
var Bird = function (idName) {
// ...
};
return Bird;
});
6.4 將模塊定義為一個(gè)函數(shù)
模塊的返回值類型 不一定是 一個(gè)對(duì)象,也可以是 一個(gè)函數(shù)腿堤。此處是一個(gè)返回了函數(shù)的模塊定義:
// Bird.js
define(['GameBg', 'GrassLand'], function (GameBg, GrassLand) {
// Bird對(duì)象的構(gòu)造函數(shù)
return function (idName) {
// ...
};
});
6.5 簡(jiǎn)單包裝CommonJS來(lái)定義模塊
define(function(require, exports, module) {
var a = require('a'),
b = require('b');
// 返回模塊
return function () {};
});
6.6 定義一個(gè)命名模塊
你可能會(huì)看到一些define()中包含了一個(gè)模塊名稱作為首個(gè)參數(shù)如暖。
這些常由優(yōu)化工具生成。你也可以自己顯式指定模塊名稱盒至,但這使模塊更不具備移植性——就是說(shuō)若你將文件移動(dòng)到其他目錄下樱衷,你就得重命名酒唉。一般最好避免對(duì)模塊硬編碼黔州,而是交給優(yōu)化工具去生成流妻。優(yōu)化工具需要生成模塊名以將多個(gè)模塊打成一個(gè)包,加快到瀏覽器的載人速度绅这。
define("FlappyBird",
["GameBg", "GrassLand"],
function(GameBg, GrassLand) {
//Define foo/title object in here.
}
);