RequireJS是一個JavaScript文件和模塊加載器痊末,可視為模塊管理工具油昂。
為什么使用RequireJS呢?
- 有效防止命名沖突
- 聲明不同JS文件之間的依賴
- JS代碼以模塊化的方式組織
RequireJS為幫助解決前端代碼庫的組織難題绑蔫,提供了兩種解決思路:
- 模塊化組織JS文件
- 異步加載JS文件
JavaScript模塊化編程
JavaScript模塊化編程目的是為了讓開發(fā)者僅需實(shí)現(xiàn)核心的業(yè)務(wù)邏輯戳表,其他都加載他人已寫好的模塊。但是JavaScript并不是一種模塊化的編程語言歌懒,雖然ECMAScript6中間正式支持類和模塊啦桌。但對于之前的版本實(shí)際上是不支持類(class),也就更不用說模塊(module)及皂。
什么是模塊呢甫男?模塊是實(shí)現(xiàn)特定功能的一組方法。
模塊的原始寫法
將不同函數(shù)以及記錄狀態(tài)的變量放在一起验烧,算是一個模塊板驳。
function fn1(){...}
function fn2(){...}
缺點(diǎn):污染全局變量,無法保證與其他模塊發(fā)生變量命名沖突碍拆,而且模塊成員之間看不出直接關(guān)系若治。
模塊的對象寫法
將模塊定義為一個對象,所有模塊成員都放在對象里面感混。
//將屬性和操作都封裝在對象中
var module = new Object({
_prop:0,
fn1:function(){...},
fn2:function(){...}
});
// 使用時直接調(diào)用對象的屬性
module.fn1();
缺點(diǎn):暴露了模塊成員端幼,內(nèi)部狀態(tài)可被外部改寫。
module._prop = 100;
立即執(zhí)行函數(shù)寫法
立即執(zhí)行函數(shù)(IIFE, Immediately-Invoked Function Expression)可達(dá)到不暴露私有成員的目的弧满。
var module = (function(){
var _prop = 0;
var fn1 = function(){...};
var fn2 = function(){...};
return {fn1:fn1, fn2:fn2};
})();
放大模式
如果一個模塊很大婆跑,必須分成幾個部分,或者是一個模塊需要繼承另一個模塊庭呜,此時就有必要采用放大模式(augmentation)滑进。
var module = (function(mod){
mod.fn = function(){...};
return mod;
})(module);
寬放大模式
瀏覽器環(huán)境中模塊各部分通常是從網(wǎng)上獲取的,有時不知道那個部分會首先加載募谎。采用放大模式扶关,第一個執(zhí)行的部分可能加載一個不存在的空對象,此時需采用“寬放大模式(Loose Augmentation)”数冬。
var module = (function(mod){
mod.fn = function(){...};
return mod;
})(window.module || {});
輸入全局變量
獨(dú)立性是模塊的重要特點(diǎn)节槐,模塊內(nèi)部最好不要與程序其他部分直接交互。為了在模塊內(nèi)調(diào)用全局變量吉执,必須顯式地將其他變量輸入模塊疯淫。
var module = (function($){
})(jQuery);
AMD規(guī)范
為什么模塊很重要呢?如何規(guī)范地使用模塊呢戳玫?
因?yàn)橛辛四K就可很方便地使用別人的代碼,想要什么樣的功能就可加載什么模塊未斑。不過前提是大家必須以同樣的方式編寫模塊咕宿。而JS模塊目前還沒有官方規(guī)范,通行的JS模塊規(guī)范有2種方式:CommonJS和AMD。
CommonJS
老實(shí)說在瀏覽器環(huán)境下府阀,沒有模塊并不是特別大的問題缆镣,畢竟網(wǎng)頁程序的復(fù)雜性有限。但對于服務(wù)端试浙,一定要有模塊董瞻,與操作系統(tǒng)和其他應(yīng)用程序交互,否則根本無法編程田巴。
2009年钠糊,美國程序員Ryan Dahl創(chuàng)建了NodeJS項(xiàng)目,將JS用于服務(wù)端編程壹哺。由此標(biāo)志著JS模塊化編程的正式誕生抄伍。
NodeJS的模塊系統(tǒng)是參照CommonJS規(guī)范實(shí)現(xiàn)的,在CommonJS中有一個全局方法require()
管宵,用于加載模塊截珍。
var math = require("math");
math.add(1, 2);
自從有了JS服務(wù)端模塊以后,對于客戶端模塊箩朴,如何做到兼容岗喉,使得一個模塊不用修改就可以在服務(wù)端和客戶端瀏覽器上都能運(yùn)行呢?由于一個重大的局限炸庞,使得CommonJS規(guī)范不適用于瀏覽器環(huán)境沈堡。問題是對于服務(wù)器而言模塊都放在本地,可同步加載等待時間只是硬盤讀取時間燕雁。但是當(dāng)瀏覽器中使用服務(wù)端的模塊時诞丽,等待時間取決于網(wǎng)速快慢,長時間的等待會造成瀏覽器處于“假死”狀態(tài)拐格。
因此瀏覽器端的模塊不能采用“同步加載(synchronous)”僧免,只能采用“異步加載(asynchronous)”方式,這就是AMD規(guī)范誕生的背景捏浊。
AMD
AMD(Asynchronous Module Definition)異步模塊加載懂衩,模塊加載不影響后續(xù)語句的執(zhí)行。所有依賴于模塊的語句都定義在一個回調(diào)函數(shù)中金踪,等到加載完成后浊洞,回調(diào)函數(shù)才會執(zhí)行。
AMD也采用了require()
語句加載模塊胡岔,不同于CommonJS的是法希,它要求兩個參數(shù)。
// module參數(shù)為一個數(shù)組靶瘸,里面的成員是要加載的模塊
// callback參數(shù)是模塊加載成功后執(zhí)行的回調(diào)函數(shù)
require([module], callback)
// math模塊與math.add()加載不是同步的苫亦,瀏覽器不會發(fā)生假死毛肋,因此AMD比較適合瀏覽器環(huán)境。
require(["math"], function(math){
math.add(1, 2);
});
RequireJS
早期JS代碼都會寫在一個文件中屋剑,僅需加載一個文件即可润匙。后來代碼越來越多,必須分割成多個文件唉匾,依次加載孕讳。問題是這種加載的方式,瀏覽器會停止頁面渲染巍膘,加載文件越多厂财,網(wǎng)頁失去響應(yīng)的時間越長。另外JS文件之間存在依賴關(guān)系典徘,必須嚴(yán)格保證加載順序蟀苛。當(dāng)依賴關(guān)系非常復(fù)雜的時候,代碼的編寫和維護(hù)變得異常困難逮诲。
RequireJS的誕生是為了解決這兩個問題:
- 實(shí)現(xiàn)JS文件的異步加載避免頁面失去響應(yīng)
- 管理模塊之間的依賴關(guān)系帜平,便于代碼編寫和維護(hù)。
加載資源文件
<script src="https://cdn.bootcss.com/require.js/2.3.5/require.js"></script>
在引入require.js
文件之后梅鹦,整個windows
對象就有require()
方法裆甩。可通過require()
方法來加載其他JS文件齐唆。RequireJS的入口是引入時指定的data-main
屬性嗤栓,在RequireJS引入后,會自動執(zhí)行指向data-main
屬性所指定的入口文件箍邮。data-main="js/main"
表示讓RequireJS去js
目錄下尋找main.js
文件茉帅,默認(rèn)main.js
是項(xiàng)目全局配置文件。
由于引入RequireJS文件本身可能會造成頁面失去響應(yīng)锭弊,解決的方式可將其放在網(wǎng)頁底部加載堪澎,或使用延遲加載。
<script src="./assets/scripts/require-2.3.5.js" data-main="js/main" async="true" defer></script>
async="true"
的作用和jQuery中AJAX的async=true
的目的一樣味滞,表示一邊加載RequireJS一邊執(zhí)行它樱蛤。如果設(shè)置為false
則表示等待RequireJS完全加載完成后才執(zhí)行,這種方式的缺陷是在網(wǎng)絡(luò)延遲較大時頁面會出現(xiàn)空白剑鞍。如果script
標(biāo)簽不再head
中而在頁面尾部昨凡,則不會出現(xiàn)空白現(xiàn)象。另外蚁署,IE并不支持async
屬性便脊,僅支持defer
屬性。
主模塊
data-main
加載的是主模塊形用,意思是頁面的入口就轧,類似C語言的main()
函數(shù)证杭,所有代碼從此處開始運(yùn)行田度。
RequireJS以一個相對于baseUrl
的地址來加載所有代碼妒御,頁面頂層<script>
標(biāo)簽內(nèi)含有一個特殊的屬性data-main
,RequireJS使用它來啟動腳本加載過程镇饺,baseUrl
一般設(shè)置到該屬性相一致的目錄乎莉。RequireJS目的是鼓勵代碼模塊化,鼓勵在使用腳本時以module ID
替代 URL 地址奸笤。
$ vim index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>app</title>
</head>
<body>
<script type="text/javascript" src="./js/require-2.3.5.js" data-main="js/main"></script>
</body>
</html>
baseUrl
可通過requirejs.config
手動設(shè)置惋啃,若沒有顯式指定config
及data-main
,則默認(rèn)的baseUrl
為包含RequireJS的那個HTML頁面的所屬目錄监右。
$ vim js/main.js
/**
* RequireJS全局配置文件
*/
requirejs.config({
//設(shè)置項(xiàng)目路徑边灭,項(xiàng)目會以baseUrl作為相對路徑去查找模塊文件
baseUrl:"./js",
//預(yù)加載JS文件的配置項(xiàng),默認(rèn)可不用添加.js后綴
paths:{
//RequireJS默認(rèn)假定所有的依賴資源都是JS腳本健盒,因此無需再module ID上再加上js后綴绒瘦。
jquery:"../scripts/jquery-3.3.1"
}
});
正常情況下,主模塊是依賴于其他模塊的扣癣,此時就要使用AMD規(guī)范定義的require()
函數(shù)惰帽。
require([module], function(module){...});
/**
* RequireJS全局配置文件
*/
requirejs.config({
//設(shè)置項(xiàng)目路徑,項(xiàng)目會以baseUrl作為相對路徑去查找模塊文件
baseUrl:"./js",
//預(yù)加載JS文件的配置項(xiàng)父虑,默認(rèn)可不用添加.js后綴
paths:{
//RequireJS默認(rèn)假定所有的依賴資源都是JS腳本该酗,因此無需再module ID上再加上js后綴。
jquery:"https://cdn.bootcss.com/jquery/3.3.1/jquery",
bootstrap:"https://cdn.bootcss.com/bootstrap/4.1.1/js/bootstrap"
}
});
requirejs(['jquery', 'bootstrap'],function($, undefined){
});
RequireJS要求每個模塊是一個的單獨(dú)的JS文件士嚎,如果加載多個模塊會發(fā)出多次HTTP請求呜魄,會影響頁面的加載速度。
RequireJS加載的模塊采用AMD規(guī)范莱衩,也就是說模塊必須按照AMD的規(guī)定來書寫爵嗅。具體說來模塊必須采用特定的define()
函數(shù)來定義,如果一個模塊不依賴其他模塊膳殷,可直接定義在define()
函數(shù)之中操骡。
但是實(shí)際上,雖然部分流行的函數(shù)庫符合AMD規(guī)范赚窃,但更多的庫并不符合册招。RequireJS如何加載非規(guī)范的模塊呢?在使用require()
之前勒极,需在require.config()
函數(shù)中定義非規(guī)范模塊的特征是掰。
require.config()
接收一個配置對象,此對象除了paths
屬性之外辱匿,還有一個shim
屬性键痛,專門用來配置不兼容的模塊炫彩。每個模塊需要定義exports
值即輸出的變量名,表明這個模塊外部調(diào)用名稱絮短。其次deps
數(shù)組屬性表明該模塊的依賴性江兢。
RequireJS常用方法
requirejs.config()
require()
define()
RequireJS源碼解析
RequireJS工作流程
- 載入模塊
- 通過模塊名解析出模塊信息并計算出URL
- 通過創(chuàng)建
script
的形式將模塊加載到頁面 - 判斷被加載腳本若存在依賴則加載,若不存在則直接執(zhí)行
factory()
丁频。 - 等待所有腳本都加載完畢后執(zhí)行回調(diào)函數(shù)
// 定義全局變量
var requirejs,require,define;
// 自執(zhí)行函數(shù)
(function(global, setTimeout){
//...
})(this, (typeof setTimeout==='undefined'?undefined:setTimeout));
RequireJS可分為三部分
- 定義全局變量和幫助函數(shù)
- 模塊加載核心部分
- 定義
require
和define
方法以及項(xiàng)目入口