RequireJs已經(jīng)流行很久了废士,它提供了以下功能:
- 聲明不同js文件之間的依賴
- 可以按需、并行、延時載入js庫
- 可以讓我們的代碼以模塊化的方式組織
- 初看起來并不復雜。
在html中引入requirejs
在HTML中禁悠,添加這樣的 < script> 標簽:
<script src="/path/to/require.js" data-main="/path/to/app/config.js"></script>
通常使用requirejs的話,我們只需要導入requirejs即可汰蓉,不需要顯式導入其它的js庫绷蹲,因為這個工作會交給requirejs來做棒卷。
屬性 data-main 是告訴requirejs:你下載完以后顾孽,馬上去載入真正的入口文件。它一般用來對requirejs進行配置比规,并且載入真正的程序模塊若厚。
在config.js中配置requirejs
config.js 中通常用來做兩件事:
1、配置requirejs 比如項目中用到哪些模塊蜒什,文件路徑是什么
2测秸、載入程序主模塊
requirejs.config({
baseUrl: '/public/js',
paths: {
app: 'app'
}
});
requirejs(['app'], function(app) {
app.hello();
});
在 paths 中,我們聲明了一個名為 app 的模塊灾常,以及它對應的js文件地址霎冯。在最理想的情況下, app.js 的內(nèi)容钞瀑,應該使用requirejs的方式來定義模塊:
define([], function() {
return {
hello: function() {
alert("hello, app~");
}
}
});
這里的 define 是requirejs提供的函數(shù)沈撞。requirejs一共提供了兩個全局變量:
1、requirejs/require: 用來配置requirejs及載入入口模塊雕什。如果其中一個命名被其它庫使用了缠俺,我們可以用另一個 define: 定義一個模塊
2、另外還可以把 require 當作依賴的模塊贷岸,然后調(diào)用它的方法:
define(["require"], function(require) {
var cssUrl = require.toUrl("./style.css");
});
依賴一個不使用requirejs方式的庫
前面的代碼是理想的情況壹士,即依賴的js文件,里面用了 define(...) 這樣的方式來組織代碼的偿警。如果沒用這種方式躏救,會出現(xiàn)什么情況?
比如這個 hello.js :
function hello() {
alert("hello, world~");
}
它就按最普通的方式定義了一個函數(shù)螟蒸,我們能在requirejs里使用它嗎落剪?
先看下面不能正確工作的代碼:
requirejs.config({
baseUrl: '/public/js',
paths: {
hello: 'hello'
}
});
requirejs(['hello'], function(hello) {
hello();
});
這段代碼會報錯,提示:
Uncaught TypeError: undefined is not a function
原因是最后調(diào)用 hello() 的時候尿庐,這個 hello 是個 undefined . 這說明忠怖,雖然我們依賴了一個js庫(它會被載入),但requirejs無法從中拿到代表它的對象注入進來供我們使用抄瑟。
在這種情況下凡泣,我們要使用 shim 枉疼,將某個依賴中的某個全局變量暴露給requirejs,當作這個模塊本身的引用鞋拟。
requirejs.config({
baseUrl: '/public/js',
paths: {
hello: 'hello'
},
shim: {
hello: { exports: 'hello' }
}
});
requirejs(['hello'], function(hello) {
hello();
});
再運行就正常了骂维。
上面代碼 exports: 'hello' 中的 hello ,是我們在 hello.js 中定義的 hello 函數(shù)贺纲。當我們使用 function hello() {} 的方式定義一個函數(shù)的時候航闺,它就是全局可用的。如果我們選擇了把它 export 給requirejs猴誊,那當我們的代碼依賴于 hello 模塊的時候潦刃,就可以拿到這個 hello 函數(shù)的引用了。
所以: exports 可以把某個非requirejs方式的代碼中的某一個全局變量暴露出去懈叹,當作該模塊以引用乖杠。
暴露多個變量:init
但如果我要同時暴露多個全局變量呢?比如澄成, hello.js 的定義其實是這樣的:
function hello() {
alert("hello, world~");
}
function hello2() {
alert("hello, world, again~");
}
它定義了兩個函數(shù)胧洒,而我兩個都想要。
這時就不能再用 exports 了墨状,必須換成 init 函數(shù):
requirejs.config({
baseUrl: '/public/js',
paths: {
hello: 'hello'
},
shim: {
hello: {
init: function() {
return {
hello: hello,
hello2: hello2
}
}
}
}
});
requirejs(['hello'], function(hello) {
hello.hello1();
hello.hello2();
});
當 exports 與 init 同時存在的時候卫漫, exports 將被忽略。
無主的與有主的模塊
我遇到了一個折騰我不少時間的問題:為什么我只能使用 jquery 來依賴jquery, 而不能用其它的名字肾砂?
比如下面這段代碼:
requirejs.config({
baseUrl: '/public/js',
paths: {
hello: 'hello'
},
shim: {
hello: {
init: function() {
return {
hello: hello,
hello2: hello2
}
}
}
}
});
requirejs(['hello'], function(hello) {
hello.hello1();
hello.hello2();
});
它會提示我:
jq is undefined
但我僅僅改個名字:
requirejs.config({
baseUrl: '/public/js',
paths: {
jquery: 'lib/jquery/jquery'
}
});
requirejs(['jquery'], function(jq) {
alert(jq);
});
就一切正常了列赎,能打印出 jq 相應的對象了。
為什么通今?我始終沒搞清楚問題在哪兒粥谬。
有主的模塊
經(jīng)常研究,發(fā)現(xiàn)原來在jquery中已經(jīng)定義了:
define('jquery', [], function() { ... });
它這里的 define 跟我們前面看到的 app.js 不同辫塌,在于它多了第一個參數(shù) 'jquery' 漏策,表示給當前這個模塊起了名字 jquery ,它已經(jīng)是有主的了,只能屬于 jquery .
所以當我們使用另一個名字:
myjquery: 'lib/jquery/jquery'
去引用這個庫的時候,它會發(fā)現(xiàn)集乔,在 jquery.js 里聲明的模塊名 jquery 與我自己使用的模塊名 myjquery 不能,便不會把它賦給 myjquery 感耙,所以 myjquery 的值是 undefined 。
所以我們在使用一個第三方的時候持隧,一定要注意它是否聲明了一個確定的模塊名即硼。
無主的模塊
如果我們不指明模塊名,就像這樣:
define([...], function() {
...
});
那么它就是無主的模塊屡拨。我們可以在 requirejs.config 里只酥,使用任意一個模塊名來引用它褥实。這樣的話,就讓我們的命名非常自由裂允,大部分的模塊就是無主的损离。
為什么有的有主,有的無主
可以看到绝编,無主的模塊使用起來非常自由僻澎,為什么某些庫(jquery, underscore)要把自己聲明為有主的呢?
按某些說法十饥,這么做是出于性能的考慮窟勃。因為像 jquery , underscore 這樣的基礎庫,經(jīng)常被其它的庫依賴绷跑。如果聲明為無主的拳恋,那么其它的庫很可能起不同的模塊名凡资,這樣當我們使用它們時砸捏,就可能會多次載入jquery/underscore。
而把它們聲明為有主的隙赁,那么所有的模塊只能使用同一個名字引用它們垦藏,這樣系統(tǒng)就只會載入它們一次。
挖墻角
對于有主的模塊伞访,我們還有一種方式可以挖墻角:不把它們當作滿足requirejs規(guī)范的模塊掂骏,而當作普通js庫,然后在 shim 中導出它們定義的全局變量厚掷。
requirejs.config({
baseUrl: '/public/js',
paths: {
myjquery: 'lib/jquery/jquery'
},
shim: {
myjquery: { exports: 'jQuery' }
}
});
requirejs(['myjquery'], function(jq) {
alert(jq);
});
這樣通過暴露 jQuery 這個全局變量給 myjquery 弟灼,我們就能正常的使用它了。
不過我們完全沒有必要這么挖墻角冒黑,因為對于我們來說田绑,似乎沒有任何好處。
如何完全不讓jquery污染全局的$
在前面引用jquery的這幾種方式中抡爹,我們雖然可以以模塊的方式拿到jquery模塊的引用掩驱,但是還是可以在任何地方使用全局變量 jQuery 和 $ 。有沒有辦法讓jquery完全不污染這兩個變量冬竟?
在init中調(diào)用noConflict (無效)
首先嘗試一種最簡單但是不工作的方式:
requirejs.config({
baseUrl: '/public/js',
paths: {
jquery: 'lib/jquery/jquery'
},
shim: {
jquery: {
init: function() {
return jQuery.noConflict(true);
}
}
}
});
requirejs(['jquery'], function(jq) {
alert($);
});
這樣是不工作的欧穴,還是會彈出來一個非 undefined 的值。其原因是泵殴,一旦requirejs為模塊名 jquery 找到了屬于它的模塊涮帘,它就會忽略 shim 中相應的內(nèi)容。也就是說笑诅,下面這段代碼完全沒有執(zhí)行:
jquery: {
init: function() {
return jQuery.noConflict(true);
}
}
使用另一個名字
如果我們使用挖墻角的方式來使用jquery调缨,如下:
requirejs.config({
baseUrl: '/public/js',
paths: {
myjquery: 'lib/jquery/jquery'
},
shim: {
myjquery: {
init: function() {
return jQuery.noConflict(true);
}
}
}
});
requirejs(['myjquery'], function(jq) {
alert($);
});
這樣的確有效映屋,這時彈出來的就是一個 undefined 。但是這樣做的問題是同蜻,如果我們引用的某個第三方庫還是使用 jquery 來引用jquery棚点,那么就會報“找不到模塊”的錯了。
我們要么得手動修改第三方模塊的代碼湾蔓,要么再為它們提供一個 jquery 模塊瘫析。但是使用后者的話,全局變量 $ 可能又重新被污染了默责。
使用map
如果我們有辦法能讓在繼續(xù)使用 jquery 這個模塊名的同時贬循,有機會調(diào)用 jQuery.noConflict(true) 就好了。
我們可以再定義一個模塊桃序,僅僅為了執(zhí)行這句代碼:
jquery-private.js
define(['jquery'], function(jq) {
return jQuery.noConflict(true);
});
然后在入口處先調(diào)用它:
requirejs.config({
baseUrl: '/public/js',
paths: {
jquery: 'lib/jquery/jquery',
'jquery-private': 'jquery-private'
}
});
requirejs(['jquery-private', 'jquery'], function() {
alert($);
});
這樣的確可行杖虾,但是還是會有問題: 我們必須小心的確保 jquery-private 永遠是第一個被依賴,這樣它才有機會盡早調(diào)用 jQuery.noConflict(true) 清除全局變量 $ 和 jQuery 媒熊。這種保證只能靠人奇适,非常不可靠。
我們這時可以引入 map 配置芦鳍,一勞永逸地解決這樣問題:
requirejs.config({
baseUrl: '/public/js',
paths: {
jquery: 'lib/jquery/jquery',
'jquery-private': 'jquery-private'
},
map: {
'*': { 'jquery': 'jquery-private'},
'jquery-private': { 'jquery': 'jquery'}
}
});
requirejs(['jquery'], function(jq) {
alert($);
});
這樣做嚷往,就解決了前面的問題:在除了jquery-private之外的任何依賴中,還可以直接使用 jqurey 這個模塊名柠衅,并且總是被替換為對 jquery-private 的依賴皮仁,使得它最先被執(zhí)行。
小結(jié)
與requirejs類似的還有seajs等等菲宴。javascript模塊化編程很重要贷祈,requirejs屬于AMD規(guī)范,關于AMD 規(guī)范喝峦,請看:https://github.com/amdjs/amdjs-api/wiki/AMD
假如您也是前端势誊,可以在項目中用一下!