requirejs中define和require的定位以及使用差別?
define定義require加載使用,define定義的子產品可以被其它調用 require不行.
define和require在依賴處理和回調執行上都是一樣的,不一樣的地方是define的回調函數需要有return語句傳回子產品對象,這樣define定義的子產品才能被其他子產品引用;require的回調函數不需要return語句
define([require,path/myMod1,path/moMod2], function(require){
var mod1 = require('path/myMod1'), mod2 = require('path/myMod2');
})
其實邏輯上類似于
define([require], function(require){
var mod1 = require('path/myMod1'), mod2 = require('path/myMod2');
})
你隻是把參數丢了而已,是以這樣也行
define([require,path/myMod1,path/moMod2], function(require){
var mod1 = arguments[1], mod2 = arguments[2];
})
是以,可以這麼用
define([require,path/myMod1,path/moMod2], function(require,mod1,mod2){
// 這裡 mod1 和 mod2 都準備好了,還可以用require繼續加載别的
})
或者,改成你看到的那些例子
define([path/myMod1,path/moMod2], function(mod1,mod2){
})
是以
define(['jquery.validate'], function(validate){
// 這裡可以用validate,但是用不了jquery了?
})
這樣本質上并沒有問題,隻不過你引用了這個插件,必然也是想用jquery的而不隻是validate,
是以
define(['jquery', 'jquery.validate'], function(jquery, validate){
// 這樣才有jquery
})
像你一開始那樣使用也行
define(['require', 'jquery.validate'], function(require, validate){
var jquery = require('jquery');
})
其實這裡又要扯到AMD和CMD的差別,依賴前置和依賴就近。
define([path/myMod1,path/moMod2], function(mod1,mod2){
})
是依賴前置。
define(function(require, exports, module) {
var $ = require('jquery');
})
這是依賴後置。
後者可以用來解決循環引用問題。。
第二個問題: 關于requirejs.config 中shim
題主了解有點偏差,requirejs.config 中shim作用是 為那些沒有使用define()來聲明依賴關系、設定子產品的"浏覽器全局變量注入"型腳本做依賴和導出配置。
簡單來說就是對requirejs要引用的第三方非AMD規範的插件、類庫的特殊處理。通常有兩種方法:
A: AMD化.通過define封裝一下
B: config shim.
另外使用seajs的時候也要注意非CMD插件的CMD化。
補充:require和define差別。使用方法一樣
define(['./jquery'], function($) { console.log($); });
require(['./jquery'], function($) { console.log($); });
都能加載到jquery. 但是define與require不同的就是它多出來一個接口的傳回。是以當把define寫成require後你會發現别的module引用不到本次定義的module.
使用例子
requirejs.config({
baseUrl: 'libs',
paths: {
"core1": 'cores/core1',
"core2": 'cores/core2',
"util1": 'utils/util1',
"util2": 'utils/util2',
"service1": 'services/service1',
"service2": 'services/service2',
}
});
require(["core1","core2","util1","util2","service1","service2"], function() {
var core1 = require("core1");
var core2 = require("core2");
var util1 = require("util1");
var util2 = require("util2");
var service1 = require("service1");
var service2 = require("service2");
});
- 聲明不同js檔案之間的依賴
- 可以按需、并行、延時載入js庫
- 可以讓我們的代碼以子產品化的方式組織
初看起來并不複雜。
在html中引入requirejs
在HTML中,添加這樣的
<script>
标簽:
通常使用requirejs的話,我們隻需要導入requirejs即可,不需要顯式導入其它的js庫,因為這個工作會交給requirejs來做。
屬性
data-main
是告訴requirejs:你下載下傳完以後,馬上去載入真正的入口檔案。它一般用來對requirejs進行配置,并且載入真正的程式子產品。
在config.js中配置requirejs
config.js
中通常用來做兩件事:
- 配置requirejs 比如項目中用到哪些子產品,檔案路徑是什麼
- 載入程式主子產品
requirejs.config({
baseUrl: '/public/js',
paths: {
app: 'app'
}
});
requirejs(['app'], function(app) {
app.hello();
});
在
paths
中,我們聲明了一個名為
app
的子產品,以及它對應的js檔案位址。在最理想的情況下,
app.js
的内容,應該使用requirejs的方式來定義子產品:
define([], function() {
return {
hello: function() {
alert("hello, app~");
}
}
});
這裡的
define
是requirejs提供的函數。requirejs一共提供了兩個全局變量:
- requirejs/require: 用來配置requirejs及載入入口子產品。如果其中一個命名被其它庫使用了,我們可以用另一個
- define: 定義一個子產品
另外還可以把
require
當作依賴的子產品,然後調用它的方法:
define(["require"], function(require) {
var cssUrl = require.toUrl("./style.css");
});
依賴一個不使用requirejs方式的庫
前面的代碼是理想的情況,即依賴的js檔案,裡面用了
define(...)
這樣的方式來組織代碼的。如果沒用這種方式,會出現什麼情況?
比如這個
hello.js
:
function hello() {
alert("hello, world~");
}
它就按最普通的方式定義了一個函數,我們能在requirejs裡使用它嗎?
先看下面不能正确工作的代碼:
requirejs.config({
baseUrl: '/public/js',
paths: {
hello: 'hello'
}
});
requirejs(['hello'], function(hello) {
hello();
});
這段代碼會報錯,提示:
Uncaught TypeError: undefined is not a function
原因是最後調用
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
函數。當我們使用
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);
});
它會提示我:
jq is undefined
但我僅僅改個名字:
requirejs.config({
baseUrl: '/public/js',
paths: {
jquery: 'lib/jquery/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
子產品。但是使用後者的話,全局變量
$
可能又重新被污染了。
使用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
的依賴,使得它最先被執行。