requirejs、require方法沖突
如果加載了多個(gè)requirejs腳本雹食,每個(gè)requirejs會(huì)判斷是否瀏覽器已經(jīng)實(shí)現(xiàn)了require和define方法。如果瀏覽器已經(jīng)自帶require和define方法,或者之前已經(jīng)有一個(gè)requirejs腳本執(zhí)行茸习,那么這個(gè)requirejs就會(huì)立刻停止執(zhí)行。所以壁肋,即使頁面上加載了多次requirejs腳本也不會(huì)有什么問題号胚。
我把context叫做一個(gè)命名空間,因?yàn)槊恳粋€(gè)context都有一個(gè)名字浸遗,這樣同名而功能不同的模塊就可以放在不同的context中以防沖突猫胁。
如果開發(fā)人員沒有配置context,那么跛锌,requirejs還會(huì)生成一個(gè)默認(rèn)的context弃秆,這個(gè)默認(rèn)的context配置大致如下:
requirejs.config({
context: "_",? // default context name
baseUrl: "./",
waitSeconds:7, // how long canloading last
paths: {},
bundles: {},
pkgs: {},
shim: {},
config: {}
});
注意:在不指定context名稱的情況下,任何配置和調(diào)用都是針對默認(rèn)context的修改和調(diào)用。
如果requirejs初始化時(shí)自定義配置context菠赚,那么默認(rèn)創(chuàng)建的context的name 就是”_”盼樟。如果需要添加新的context,只需指定一個(gè)新的contextName即可锈至,比如下面這個(gè)調(diào)用就會(huì)創(chuàng)建一個(gè)新的context:
requirejs({context:”new content name”});
同名的context只會(huì)有一個(gè)晨缴,配置同名的context等于修改這個(gè)context的屬性。
每個(gè)context都配置了一個(gè)加載超時(shí)的時(shí)間峡捡,某個(gè)模塊如果沒有初始化击碗,加載的時(shí)間又超過了這個(gè)時(shí)間,就會(huì)被認(rèn)為加載失敗们拙。
加載超時(shí)是針對整個(gè)context下的所有模塊而言的稍途,而不是單指某個(gè)模塊,也就是說這個(gè)默認(rèn)的7秒是指所有模塊應(yīng)該在7秒之內(nèi)全部加載完成砚婆。7秒之后械拍,如果有沒有被加載的模塊,將拋出error指示哪些模塊沒有加載装盯。(requirejs每隔50毫秒做一次判斷)坷虑。
每個(gè)context的基準(zhǔn)URL默認(rèn)值是”./”。
如果開發(fā)人員沒有指定context名稱埂奈,那么這個(gè)第一個(gè)context就是requirejs默認(rèn)生成的context迄损,否則就是開發(fā)人員自己定義的context。在不指定基準(zhǔn)URL的前提下账磺,第一個(gè)context的基準(zhǔn)URL設(shè)定比較特殊芹敌,除了標(biāo)準(zhǔn)的設(shè)定方法(參考后面的基準(zhǔn)URL標(biāo)準(zhǔn)設(shè)定方法),還可以使用以下兩種特殊方式設(shè)置:
第一種:通過requirejs或require對象配置
在確認(rèn)requirejs腳本之前沒有其它requirejs執(zhí)行過的前提下:
requirejs={baseUrl: './'}
或
require={baseUrl: './'}
注意:通過這種方式設(shè)置基準(zhǔn)URL垮抗,data-main指定的腳本文件位置也會(huì)變成相對于基準(zhǔn)URL的路徑氏捞,因?yàn)閐ata-main指定的腳本本身只是依賴的關(guān)系之一。而且冒版,data-main指定的腳本也屬于第一個(gè)context液茎。
比如下面這種情況:
requirejs={baseUrl: 'scripts/lib'}
腳本模塊app.js的最終路徑變成了"scripts/lib/scripts/app.js",不是原來的"scripts/app.js"壤玫,而且它的依賴名稱也會(huì)變?yōu)閟cripts/lib/scripts/app(requirejs默認(rèn)會(huì)去掉腳本路徑的最后一個(gè)“.js”豁护,除非data-main的值以“/”開頭哼凯,或包含“:”欲间,或包含“?”)。
第二種:根據(jù)script元素的data-main屬性指定的腳本路徑計(jì)算
如果沒有設(shè)定baseUrl断部,requirejs會(huì)根據(jù)script元素data-main屬性指定的JavaScript文件路徑計(jì)算出一個(gè)基準(zhǔn)URL猎贴。
比如data-main="scripts/app.js",那么baseUrl就是"scripts/":
基準(zhǔn)URL標(biāo)準(zhǔn)設(shè)定方法
除了第一個(gè)context可以使用上面的方法,其它自定義的context配置baseUrl就只能使用下面這兩種方法她渴。但這兩種方法同樣也可以用來修改第一個(gè)context的屬性达址,在不指定context名稱的情況下,其實(shí)就是修改第一個(gè)context趁耗。
通過requirejs或require方法(這兩個(gè)本身就是同一個(gè)方法)設(shè)置
以下兩個(gè)等于把默認(rèn)命名空間的基準(zhǔn)URL設(shè)置成了scripts/lib:
requirejs({baseUrl:'scripts/lib'});
require({baseUrl:'scripts/lib'});
通過requirejs.config方法
requirejs.config({baseUrl:'scripts/lib'});
require.config({baseUrl:'scripts/lib'});
其實(shí)config方法調(diào)用的就是requirejs方法沉唠,所以它們是一樣的。
模塊依賴是指個(gè)模塊之間的相互依賴關(guān)系苛败,腳本運(yùn)行時(shí)满葛,只有當(dāng)依賴的模塊全部加載完成之后,當(dāng)前腳本才會(huì)執(zhí)行罢屈,這就是依賴關(guān)系的作用嘀韧。
依賴關(guān)系使用數(shù)組配置,數(shù)組元素為字符串(即模塊的名稱)缠捌,一般是相對于baseUrl的路徑锄贷,只不過沒有文件后綴。而且曼月,為了比較方便的獲取模塊入口谊却,模塊一般會(huì)通過define方法定義。因?yàn)檠魄郏ㄟ^define定義的模塊因惭,可以被依賴數(shù)組后面的回調(diào)函數(shù)直接獲取并使用。
以jQuery為例绩衷,在jQuery腳本的末尾一般有下面兩行代碼:
if(typeof define === "function"&& define.amd && define.amd.jQuery) {
define("jquery", [], function () { return jQuery; } );
}
再以underscore為例蹦魔,在腳本末尾有下面幾行代碼:
if (typeof define === 'function' && define.amd) {
define('underscore', [],function() {
return _;
});
}
使用require配置依賴模塊的時(shí)候,只是聲明了模塊的名稱咳燕,卻不知道模塊的具體位置勿决。在沒有特殊聲明的情況下,requirejs認(rèn)為模塊名和文件名相同招盲,因此低缩,只要兩者一致,requirejs就可以正確找到腳本文件曹货。但如果不同咆繁,就需要通過path配置:
requirejs.config({
baseUrl:"scripts/lib",
paths: {
jquery:'jquery-1.7.2'
}
});
這樣,requirejs就知道jquery模塊位于scripts/lib/jquery-1.7.2.js文件中顶籽。
requirejs={deps: ['jquery']}
與基準(zhǔn)URL的方式一樣玩般,既可以通過requirejs方法,也可通過requirejs.config方法配置礼饱。
如果一個(gè)JS文件中有多個(gè)模塊坏为,就可以使用模塊束的方式聲明:
requirejs.config({
baseUrl:"scripts/lib",
bundles: {
jsUtil:['MathUtil', 'DateUtil']
}
});
上面這個(gè)例子就是說在scripts/lib/jsUtils.js文件中有MathUtil和DateUtil這兩個(gè)子模塊究驴。
如果一個(gè)文件夾中有多個(gè)JS文件,使用path的方式寫全就需要很多行代碼匀伏,這個(gè)時(shí)候如果使用包的方式聲明就可以省去很多麻煩:
requirejs.config({
baseUrl:"scripts/lib",
pkgs: [{name:'jqueryui',location: 'jqueryui/',main: 'core'}]
});
這樣定義之后洒忧,凡是在scripts/lib/jqueryui/目錄下的模塊就可以通過這種方式正確找到:
require(['jqueryui/button', 'jqueryui/dialog']);
上面這個(gè)例子就是獲取scripts/lib/jqueryui/button.js和scripts/lib/jqueryui/dialog.js的例子。另外够颠,因?yàn)閖queryui是一個(gè)目錄熙侍,并不對應(yīng)一個(gè)JS文件,所以又有一個(gè)main屬性履磨,這個(gè)屬性一般對應(yīng)這個(gè)JS包中的主程序文件核行。上面的例子中,jqueryui的主程序就在scripts/lib/jqueryui/core.js中蹬耘。
并不是所有的JS模塊都會(huì)像jquery和underscore那樣調(diào)用define方法定義自己芝雪,這樣requirejs就不知道你這個(gè)模塊的入口在哪,該通過哪個(gè)對象來調(diào)用這個(gè)模塊综苔,特別是那些早版本的JS模塊惩系,因?yàn)槟鞘沁€沒有define和require的概念。
requirejs.config({
baseUrl:"scripts/lib",
shim: {jquery: {deps:[],exportsFn: func, exports:'jQuery',init:func}}
});
雖然模塊沒有使用define方法定義自己如筛,但開發(fā)人員應(yīng)該是知道如何獲取文件中的模塊的堡牡,所以,requirejs提供了兩種方式讓開發(fā)人員把模塊對象返回給requirejs管理:
在exportsFn或init方法中設(shè)置杨刨,然后作為返回值晤柄;
使用exports設(shè)置,比如”a.b.c”妖胀,那requirejs就知道通過window.a.b.c可以獲取芥颈。
先來看問題:一些第三方JS插件的依賴關(guān)系是事先設(shè)定好的,不太好修改依賴模塊的名稱赚抡,而如果某個(gè)模塊有多個(gè)版本或有其他模塊和它同名爬坑,則使用上面的配置都無法解決問題。比如path只是解決模塊名稱到路徑的問題涂臣,而這個(gè)面對的是切換模塊名稱的問題盾计。于是requirejs提出了映射的概念,根據(jù)當(dāng)前腳本的名稱動(dòng)態(tài)修改所依賴模塊的ID赁遗,是它指向正確的模塊署辉。
假如在你的硬盤下有以下幾個(gè)模塊:
foo1.0.js
foo1.2.js
some/newmodule.js
some/oldmodule.js
在newmodule.js和oldmodule.js中都有require(‘foo’)調(diào)用,要解決沖突只需要這樣配置即可:
requirejs.config({
map: {
'some/newmodule':{
'foo': 'foo1.2'
},
'some/oldmodule':{
'foo':'foo1.0'
}
}
});
不管頁面上有多少個(gè)script元素有data-main屬性岩四,requirejs只認(rèn)最后一個(gè)script元素的data-main屬性哭尝,忽略其他script元素的data-main屬性。
Requirejs獲取data-main屬性之后炫乓,并沒有立即執(zhí)行data-main指定的腳本文件(因?yàn)檫@個(gè)腳本文件可能還依賴了其他模塊)刚夺,而是把它作為了一個(gè)被依賴的模塊献丑,加入到第一個(gè)context的依賴數(shù)組中末捣。比如下面這種情況就是把scripts/app這個(gè)模塊加到一個(gè)名叫l(wèi)inus的context中:
requirejs={
context: 'linus',
baseUrl:"./",
skipDataMain: false
};
在瀏覽器中侠姑,有一個(gè)選項(xiàng)叫skipDataMain,可以讓requirejs忽略script元素的data-main箩做。在默認(rèn)情況下莽红,requirejs成功加載之后,會(huì)立馬查找頁面上所有script元素邦邦,并且把最后一個(gè)有data-main屬性的script元素的data-main最為主程序入口安吁。
requirejs={skipDataMain:true}
模塊定義 – define(name, deps, callback)
你會(huì)發(fā)現(xiàn)define方法沒有指定context名稱,這是因?yàn)閐efine方法只調(diào)用于被依賴的模塊中燃辖,而require方法已經(jīng)為依賴的模塊指定了context名稱鬼店,所以,這個(gè)模塊被哪個(gè)context需要黔龟,它就屬于那一個(gè)context妇智。
參數(shù)name是模塊的名稱,deps是該模塊所依賴的其他模塊的名稱氏身,callback一般返回該模塊的實(shí)際可被使用對象巍棱。比如jQuery的模塊定義回調(diào)函數(shù)返回的就是jQuery對象。
這種error就是瀏覽器自帶的Error對象蛋欣,只不過requirejs給它附加了其他屬性航徙。
message的格式為:msg + '\nhttp://requirejs.org/docs/errors.html#' + id。
error.requireType就是就是message后面的id陷虎;
error.requireModules一般指需要加載卻沒加載成功的模塊名稱到踏;
error.originalError是指發(fā)生其他錯(cuò)誤導(dǎo)致模塊加載失敗的原始error對象。