更多内容檢視API:http://www.requirejs.cn/
使用了塊作用域來申明function防止污染全局變量
聲明不同js檔案之間的依賴
可以按需、并行、延時載入js庫
可以讓我們的代碼以子產品化的方式組織
通常使用requirejs的話,我們隻需要導入requirejs即可,不需要顯式導入其它的js庫,因為這個工作會交給requirejs來做。
data-main
是告訴requirejs:你下載下傳完以後,馬上去載入真正的入口檔案。它一般用來對requirejs進行配置,并且載入真正的程式子產品。
<script src="/path/to/require.js" data-main="/path/to/app/config.js"></script>
requirejs下。其主要API主要是下面三個函數:
- define– 該函數使用者建立子產品。每個子產品擁有一個唯一的子產品ID,它被用于RequireJS的運作時函數,define函數是一個全局函數,不需要使用requirejs命名空間.
- require– 該函數用于讀取依賴。同樣它是一個全局函數,不需要使用requirejs命名空間.
- config– 該函數用于配置RequireJS.
require.config配置參數選項
- baseUrl——用于加載子產品的根路徑。
- paths——用于映射不存在根路徑下面的子產品路徑。
- shims——配置在腳本/子產品外面并沒有使用RequireJS的函數依賴并且初始化函數。假設underscore并沒有使用 RequireJS定義,但是你還是想通過RequireJS來使用它,那麼你就需要在配置中把它定義為一個shim。
- deps——加載依賴關系數組
config.js 中通常用來做兩件事:
- 配置requirejs 比如項目中用到哪些子產品,檔案路徑是什麼
- 載入程式主子產品
requirejs一共提供了兩個全局變量:
- requirejs/require: 用來配置requirejs及載入入口子產品。如果其中一個命名被其它庫使用了,我們可以用另一個
- define: 定義一個子產品
另外還可以把
require
當作依賴的子產品,然後調用它的方法:
define(["require"], function(require) {
var cssUrl = require.toUrl("./style.css");
});
依賴一個不使用requirejs方式的庫
如果沒用
define(...)
定義子產品,比如這個
hello.js
,按普通方式定義了一個函數,能在requirejs裡使用它嗎?
function hello() {
alert("hello, world~");
}
先看下面不能正确工作的代碼:
requirejs.config({
baseUrl: '/public/js',
paths: {
hello: 'hello'
}
});
requirejs(['hello'], function(hello) {
hello();
});
這段代碼會報錯,提示: Uncaught TypeError: undefinedisnot a function
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
函數。當我們使用
function hello() {}
的方式定義一個函數的時候,它就是全局可用的。如果我們選擇了把它
export
給requirejs,那當我們的代碼依賴于
hello
子產品的時候,就可以拿到這個
hello
函數的引用了。
是以:
exports
可以把某個非requirejs方式的代碼中的某一個全局變量暴露出去,當作該子產品以引用。
暴露多個變量:init
如果我要同時暴露多個全局變量呢?比如,
hello.js
的定義其實是這樣的:
function hello() {
alert("hello, world~");
}
function hello2() {
alert("hello, world, again~");
}
它定義了兩個函數,而我兩個都想要。這時就不能再用
exports
了,必須換成
init
函數:
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: {
myjquery: 'lib/jquery/jquery'
}
});
requirejs(['myjquery'], function(jq) {
alert(jq);
});
它會提示我:jqisundefined
但我僅僅改個名字:
requirejs.config({
baseUrl: '/public/js',
paths: {
jquery: 'lib/jquery/jquery' "jquery" : ["http://libs.baidu.com/jquery/2.0.3/jquery", "js/jquery"], /// 這樣配置後,當百度的jquery沒有加載成功後,會加載本地js目錄下的jquery
}});requirejs(['jquery'], function(jq) { alert(jq);});
就一切正常了,能列印出
jq
相應的對象了。為什麼?
有主的子產品
經常研究,發現原來在jquery中已經定義了:
define('jquery', [], function() { ... });
它這裡的
define
跟我們前面看到的
app.js
不同,在于它多了第一個參數
'jquery'
,表示給目前這個子產品起了名字
jquery
,它已經是有主的了,隻能屬于
jquery
.
是以當我們使用另一個名字:
去引用這個庫的時候,它會發現,在
jquery.js
裡聲明的子產品名
jquery
與我自己使用的子產品名
myjquery
不同,便不會把它賦給
myjquery
,是以
myjquery
的值是
undefined
。
是以我們在使用一個第三方的時候,一定要注意它是否聲明了一個确定的子產品名。
無主的子產品
如果我們不指明子產品名,就像這樣:
define([...], function() {
...
});
那麼它就是無主的子產品。我們可以在
requirejs.config
裡,使用任意一個子產品名來引用它。這樣的話,就讓我們的命名非常自由,大部分的子產品就是無主的。
為什麼有的有主,有的無主
可以看到,無主的子產品使用起來非常自由,為什麼某些庫(jquery, underscore)要把自己聲明為有主的呢?
按某些說法,這麼做是出于性能的考慮。因為像
jquery
,
underscore
這樣的基礎庫,經常被其它的庫依賴。如果聲明為無主的,那麼其它的庫很可能起不同的子產品名,這樣當我們使用它們時,就可能會多次載入jquery/underscore。
而把它們聲明為有主的,那麼所有的子產品隻能使用同一個名字引用它們,這樣系統就隻會載入它們一次。
挖牆角
對于有主的子產品,我們還有一種方式可以挖牆角:不把它們當作滿足requirejs規範的子產品,而當作普通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中調用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
中相應的内容。也就是說,下面這段代碼完全沒有執行:
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
子產品。但是使用後者的話,全局變量
$
可能又重新被污染了。
但是如果我不使用shim這個參數的話,在最新版的requirejs2.1.15中(以前的版本我不太清楚),也可以通過require([‘XX’])來解決。
使用map
如果我們有辦法能讓在繼續使用
jquery
這個子產品名的同時,有機會調用
jQuery.noConflict(true)
就好了。
我們可以再定義一個子產品,僅僅為了執行這句代碼:
jquery-private.js
define(['jquery'], function(jq) {
return jQuery.noConflict(true);
});
然後在入口處先調用它:
requirejs.config({
baseUrl: '/public/js',
paths: {
jquery: 'lib/jquery/jquery',
'jquery-private': 'jquery-private'
}
});
requirejs(['jquery-private', 'jquery'], function() {
alert($);
});
這樣的确可行,但是還是會有問題: 我們必須小心的確定
jquery-private
永遠是第一個被依賴,這樣它才有機會盡早調用
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
的依賴,使得它最先被執行。
Map參數: Map參數是用來解決同一個子產品不同版本的問題,比如在項目開發中,開發初期使用了jquery1.7版本,但是由于業務的需求需要引入jquery1.9以上的版本時候,但是又擔心有些是依賴于jquery1.7的代碼更新到1.9以上的時候會有問題,是以可以讓一部分代碼還是依賴于jquery1.7,薪增的代碼依賴于jquery1.9.
現在我在入口檔案app.js添加如下代碼:
requirejs.config({
map: {
'app/a': {
'jquery': 'js/lib/jquery1.7.js'
},
'app/b': {
'jquery': 'js/lib/jquery1.9.1.js'
}
}
});
require(['app/a'],function(jq){
});
require(['app/b'],function(jq){
});
然後在app/a.js添加如下代碼:
// a.js
define(function (require, exports, module) {
var a = require(['jquery']);
});
在app/b.js添加如下代碼:
// b.js
define(function (require, exports, module) {
var b = require(['jquery']);
});
在app.js中
require(['app/a'],function(jq){
});時候,在加載app/a.js的時候會加載jquery1.7.js檔案,在加載app/b.js的時候會加載jquery1.9.1.js.如下截圖所示:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIiV2dhcmbw5SaxVndCpVSvwVbvNmLs92bjlWd05iMn1Wavw1LcpDc0RHaiojIsJye.png!web)
如果在app.js中把下面這行b.js代碼初始化注釋掉
require(['app/b'],function(jq){
});
那麼就隻會加載app/a.js及對應的jquery1.7.js,截圖如下:
相應的 如果把app/a.js初始化代碼注釋掉,把app/b.js代碼初始化打開,那麼隻會加載jquery1.9.1,可以看到如果我想app/b.js中使用jquery1.9的話,那麼可以這樣使用了。
config參數: config是指需要将配置資訊傳給一個子產品,這些配置往往是application級别的資訊,需要一個手段将他們向下傳遞給子產品。在requireJS中,基于requirejs.config()的config配置項來實作。要擷取這些資訊的子產品可以加載特殊的依賴 ”moudle” ,并調用module.config().
首先我們可以還是試着做demo來了解下上面話的意思吧,我現在在項目requirejs下js/app檔案下建立一個d.js. 然後在app.js初始化檔案加入如下代碼:
requirejs.config({
config: {
'app/c': {
size: 'large'
},
'app/d': {
color: 'blue'
}
}
});
require(['app/c'],function(c){
console.log(c);
});
require(['app/d'],function(dss){
console.log(d);
});
在c.js裡面這樣寫代碼:
define(function (require, exports, module) {
//其值是'large'
var size = module.config().size;
return size;
});
在控制台下運作可以看到能列印出 large值出來,這說明我們可以通過config配置項來給app/c.js傳遞一個子產品資訊,比如如上面的一個對象{size:large},而在c.js裡面直接可以通過module.config()方法來擷取size的值。
下面我們可以使用一個依賴數組來做同樣的事情,如下d.js代碼:
define(['module'], function (module) {
//Will be the value 'blue'
var color = module.config().color;
return color;
});
在控制台看 也一樣可以列印出color值出來。
如果一個子產品不依賴其他子產品,那麼可以直接定義在define()函數之中。
假定現在有一個math.js檔案,它定義了一個math子產品。那麼,math.js就要這樣寫:
// math.js
define(function (){
var add = function (x,y){
return x+y;
};
return {
add: add
};
});
加載方法如下:
// main.js
require(['math'], function (math){
alert(math.add(1,1));
});
jQuery的插件可以這樣定義:
shim: {
'jquery.scroll': {
deps: ['jquery'], deps數組,表明該子產品的依賴性。
exports: 'jQuery.fn.scroll'
}
}
require.js還提供一系列插件,實作一些特定的功能。
domready插件,可以讓回調函數在頁面DOM結構加載完成後再運作。
require(['domready!'], function (doc){
// called once the DOM is ready
});
text和image插件,則是允許require.js加載文本和圖檔檔案。
define([
'text!review.txt',
'image!cat.jpg'
],
function(review,cat){
console.log(review);
document.body.appendChild(cat);
}
);
類似的插件還有json和mdown,用于加載json檔案和markdown檔案。
- 插件形式的非AMD子產品,我們經常會用到jquery插件,而且這些插件基本都不符合AMD規範,比如jquery.form插件,這時候就需要将form插件"墊"到jquery中:
require.config({
shim: {
"underscore" : {
exports : "_";
},
"jquery.form" : {
deps : ["jquery"]
}
}
})
也可以簡寫為:
require.config({
shim: {
"underscore" : {
exports : "_";
},
"jquery.form" : ["jquery"]
}
})
這樣配置之後我們就可以使用加載插件後的jquery了
require.config(["jquery", "jquery.form"], function($){
$(function(){
$("#form").ajaxSubmit({...});
})
})
define 從名字就可以看出這個api是用來定義一個子產品
require 加載依賴子產品,并執行加載完後的回調函數(注意require中的依賴是一個數組,即使隻有一個依賴,你也必須使用數組來定義)
paths還有一個重要的功能,就是可以配置多個路徑,如果遠端cdn庫沒有加載成功,可以加載本地的庫
之前的例子中加載子產品都是本地js,但是大部分情況下網頁需要加載的JS可能來自本地伺服器、其他網站或CDN,這樣就不能通過這種方式來加載了
require.config({
paths : {
"jquery" : ["http://libs.baidu.com/jquery/2.0.3/jquery"]
}
})
callback函數中有$參數,這就是依賴的jquery子產品的輸出變量,如果你依賴多個子產品,可以依次寫入多個參數來使用:
require(["jquery","underscore"],function($, _){
$(function(){
_.each([1,2,3],alert);
})
})
如果某個子產品不輸出變量值,則沒有,是以盡量将輸出的子產品寫在前面,防止位置錯亂引發誤解
require會預設的将data-main指定的js為根路徑,
AMD子產品規範
第一種寫法:
define(function() {
return {
mix: function(source, target) { }
};
});
第二種寫法 有依賴項如下:
define(['data', 'ui'], function(data, ui) {
// init here
});
第三種寫法 直接一個對象
define({
data: [],
ui: []
});
第四種寫法 有名子產品如下:
define('index', ['data','base'], function(data, base) {
// todo
});
注:有名子產品要移動到其他目錄時,JS也要跟着改,是以代碼維護方面不好
第五種寫法 包裝子產品如下:
define(function(require, exports, module) {
var base = require('base');
exports.show = function() {
// todo with module base
}
});
注:書寫格式和nodeJS比較像,可以使用require擷取子產品,使用exports或者module.exports導出API。
書寫requireJS遵循一個檔案一個子產品。
内部機制:
RequireJS加載的每個子產品作為script Tag,使用head.appendChild()方法。
在子產品的定義時,requireJS等到所有的依賴都加載完畢,會為函數的調用計算出正确的順序,然後在函數中通過正确的順序進行調用。
requireJS函數增加了第三個參數errbacks
在子產品載入失敗回調中可以使用undef函數移除子產品的注冊。
轉載:http://www.tuicool.com/articles/jam2Anv
更多内容檢視API:http://www.requirejs.cn/