RequireJS

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è)置惋啃,若沒有顯式指定configdata-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工作流程

  1. 載入模塊
  2. 通過模塊名解析出模塊信息并計算出URL
  3. 通過創(chuàng)建script的形式將模塊加載到頁面
  4. 判斷被加載腳本若存在依賴則加載,若不存在則直接執(zhí)行factory()丁频。
  5. 等待所有腳本都加載完畢后執(zhí)行回調(diào)函數(shù)
// 定義全局變量
var requirejs,require,define;
// 自執(zhí)行函數(shù)
(function(global, setTimeout){
  //...
})(this, (typeof setTimeout==='undefined'?undefined:setTimeout));

RequireJS可分為三部分

  1. 定義全局變量和幫助函數(shù)
  2. 模塊加載核心部分
  3. 定義requiredefine方法以及項(xiàng)目入口
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杉允,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子席里,更是在濱河造成了極大的恐慌叔磷,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奖磁,死亡現(xiàn)場離奇詭異改基,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)咖为,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門秕狰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人案疲,你說我怎么就攤上這事封恰。” “怎么了褐啡?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵诺舔,是天一觀的道長。 經(jīng)常有香客問我备畦,道長低飒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任懂盐,我火速辦了婚禮褥赊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘莉恼。我一直安慰自己拌喉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布俐银。 她就那樣靜靜地躺著尿背,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捶惜。 梳的紋絲不亂的頭發(fā)上田藐,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼汽久。 笑死鹤竭,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的景醇。 我是一名探鬼主播臀稚,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼啡直!你這毒婦竟也來了烁涌?” 一聲冷哼從身側(cè)響起苍碟,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤酒觅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后微峰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舷丹,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年蜓肆,在試婚紗的時候發(fā)現(xiàn)自己被綠了颜凯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡仗扬,死狀恐怖症概,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情早芭,我是刑警寧澤彼城,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站退个,受9級特大地震影響募壕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜语盈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一舱馅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刀荒,春花似錦代嗤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至烈炭,卻和暖如春溶锭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背符隙。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工趴捅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留垫毙,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓拱绑,卻偏偏與公主長得像综芥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子猎拨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 導(dǎo)語: 之前一直有聽說RequireJS膀藐,但是一直都沒機(jī)會去了解,只知道它是一個給js做模塊化的API红省。最近在做R...
    wuqke閱讀 40,898評論 11 78
  • 為什么會出現(xiàn)這些奇奇怪怪的東西额各,是不是搞事情? 因?yàn)榍岸舜a模塊化吧恃。 什么又是代碼模塊化虾啦? 這得從JavaScri...
    阿魯提爾閱讀 742評論 0 4
  • 參考資料 RequireJS 中文網(wǎng)Javascript模塊化編程(三):require.js的用法——阮一峰 前...
    BeYanJin閱讀 7,034評論 2 12
  • 為什么要使用模塊化呻率? 最主要的目的: 解決命名沖突 依賴管理 其他價值 提高代碼可讀性 代碼解耦硬毕,提高復(fù)用性 CM...
    JamHsiao_aaa4閱讀 363評論 0 1
  • 借我十年 借我亡命天涯的勇敢 接我說得出口的旦旦誓言 借我生猛與莽撞不問明天 ... 我想你借我那么多那么多 最想...
    初安_6feb閱讀 160評論 0 0