RequireJs已經流行好久了,咱們在項目中也打算使用它。它提供瞭如下功能:javascript
初看起來並不複雜。php
在HTML中,添加這樣的 <script>
標籤:css
<script src="/path/to/require.js" data-main="/path/to/app/config.js"></script>
一般使用requirejs的話,咱們只須要導入requirejs便可,不須要顯式導入其它的js庫,由於這個工做會交給requirejs來作。html
屬性 data-main
是告訴requirejs:你下載完之後,立刻去載入真正的入口文件。它通常用來對requirejs進行配置,而且載入真正的程序模塊。java
config.js
中一般用來作兩件事:jquery
requirejs.config({
baseUrl: '/public/js', paths: { app: 'app' } }); requirejs(['app'], function(app) { app.hello(); });
在 paths
中,咱們聲明瞭一個名爲 app
的模塊,以及它對應的js文件地址。在最理想的狀況下, app.js
的內容,應該使用requirejs的方式來定義模塊:ruby
define([], function() { return { hello: function() { alert("hello, app~"); } } });
這裏的 define
是requirejs提供的函數。requirejs一共提供了兩個全局變量:app
另外還能夠把 require
看成依賴的模塊,而後調用它的方法:模塊化
define(["require"], function(require) { var cssUrl = require.toUrl("./style.css"); });
前面的代碼是理想的狀況,即依賴的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方式的代碼中的某一個全局變量暴露出去,看成該模塊以引用。
但若是我要同時暴露多個全局變量呢?好比, 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
.
因此當咱們使用另外一個名字:
myjquery: 'lib/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徹底不污染這兩個變量?
首先嚐試一種最簡單可是不工做的方式:
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
模塊。可是使用後者的話,全局變量 $
可能又從新被污染了。
若是咱們有辦法能讓在繼續使用 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
的依賴,使得它最早被執行。