定義模塊
    簡(jiǎn)單的值對(duì)
    非依賴的函數(shù)式定義
    依賴的函數(shù)式定義
    載入模塊
    模塊的返回值
          return 方式
          exports導(dǎo)出
    非標(biāo)準(zhǔn)模塊定義
    常用參數(shù)
          urlArgs
          scriptType
          waitSeconds
          deps
          callback
          config
          map
          packages
rquire 壓縮
其它問(wèn)題
    1. timeout超時(shí)問(wèn)題
    2. 循環(huán)依賴問(wèn)題
    3. CDN回退
    4. 定義AMD插件
    5. 關(guān)于require的預(yù)定義模塊
    6. 關(guān)于R.js壓縮非本地文件的問(wèn)題
    7. 關(guān)于R.js - shim功能的說(shuō)明
    8. 關(guān)于require加載CSS的問(wèn)題


基本概念

因?yàn)樽陨碓O(shè)計(jì)的不足,JavaScript 這門(mén)語(yǔ)言實(shí)際上并沒(méi)有模塊化這種概念與機(jī)制,所以想實(shí)現(xiàn)如JAVA,PHP等一些后臺(tái)語(yǔ)言的模塊化開(kāi)發(fā),那么我們必須借助 requireJS 這個(gè)前端模擬模塊化的插件,雖然我們不需要去了解它的實(shí)現(xiàn)原理,但是大致去了解它是如何工作的,我相信這會(huì)讓我們更容易上手。

requireJS使用head.appendChild()將每一個(gè)依賴加載為一個(gè)script標(biāo)簽。requireJS等待所有的依賴加載完畢,計(jì)算出模塊定義函數(shù)正確調(diào)用順序,然后依次調(diào)用它們。

requireJS的歷史發(fā)展

在說(shuō)JavaScript模塊化之前,我們不得不提CommonJS(原名叫ServerJs),這個(gè)社區(qū)可謂大牛云集,他們?yōu)镹odeJS制定過(guò)模塊化規(guī)范 Modules/1.0 ,并得到了廣泛的支持。在為JavaScript定制模塊化規(guī)范時(shí),討論的都是在 Modules/1.0 上進(jìn)行改進(jìn),但是 Modules/1.0 是專(zhuān)門(mén)為服務(wù)端制定的規(guī)范,所以要想套用在客服端環(huán)境的JS上,情況就會(huì)有很大的不同,例如,對(duì)于服務(wù)端加載一個(gè)JS文件,其耗費(fèi)的時(shí)間幾乎都是可以忽略不計(jì)的,因?yàn)檫@些都是基于本地環(huán)境,而在客戶端瀏覽器上加載一個(gè)文件,都會(huì)發(fā)送一個(gè)HTTP請(qǐng)求,并且還可能會(huì)存在跨域的情況,也就是說(shuō)資源的加載,到執(zhí)行,是會(huì)存在一定的時(shí)間消耗與延遲。

所以社區(qū)的成員們意識(shí)到,要想在瀏覽器環(huán)境中也能模塊化開(kāi)發(fā),則需要對(duì)現(xiàn)有規(guī)范進(jìn)行更改,而就在社區(qū)討論制定規(guī)范的時(shí)候內(nèi)部發(fā)生了比較大的分歧,分裂出了三個(gè)主張,漸漸的形成三個(gè)不同的派別:

1.Modules/1.x派
這一波人認(rèn)為,在現(xiàn)有基礎(chǔ)上進(jìn)行改進(jìn)即可滿足瀏覽器端的需要,既然瀏覽器端需要function包裝,需要異步加載,那么新增一個(gè)方案,能把現(xiàn)有模塊轉(zhuǎn)化為適合瀏覽器端的就行了,有點(diǎn)像“?;逝伞薄;谶@個(gè)主張,制定了Modules/Transport(http://wiki.commonjs.org/wiki/Modules/Transport)規(guī)范,提出了先通過(guò)工具把現(xiàn)有模塊轉(zhuǎn)化為復(fù)合瀏覽器上使用的模塊,然后再使用的方案。
    browserify就是這樣一個(gè)工具,可以把nodejs的模塊編譯成瀏覽器可用的模塊。(Modules/Transport規(guī)范晦澀難懂,我也不確定browserify跟它是何關(guān)聯(lián),有知道的朋友可以講一下)
    目前的最新版是Modules/1.1.1(http://wiki.commonjs.org/wiki/Modules/1.1.1),增加了一些require的屬性,以及模塊內(nèi)增加module變量來(lái)描述模塊信息,變動(dòng)不大。     
2. Modules/Async派
這一波人有點(diǎn)像“革新派”,他們認(rèn)為瀏覽器與服務(wù)器環(huán)境差別太大,不能沿用舊的模塊標(biāo)準(zhǔn)。既然瀏覽器必須異步加載代碼,那么模塊在定義的時(shí)候就必須指明所依賴的模塊,然后把本模塊的代碼寫(xiě)在回調(diào)函數(shù)里。模塊的加載也是通過(guò)下載-回調(diào)這樣的過(guò)程來(lái)進(jìn)行,這個(gè)思想就是AMD的基礎(chǔ),由于“革新派”與“?;逝伞钡乃枷霟o(wú)法達(dá)成一致,最終從CommonJs中分裂了出去,獨(dú)立制定了瀏覽器端的js模塊化規(guī)范AMD(Asynchronous Module Definition)(https://github.com/amdjs/amdjs-api/wiki/AMD)
     3. Modules/2.0派
這一波人有點(diǎn)像“中間派”,既不想丟掉舊的規(guī)范,也不想像AMD那樣推到重來(lái)。他們認(rèn)為,Modules/1.0固然不適合瀏覽器,但它里面的一些理念還是很好的,(如通過(guò)require來(lái)聲明依賴),新的規(guī)范應(yīng)該兼容這些,AMD規(guī)范也有它好的地方(例如模塊的預(yù)先加載以及通過(guò)return可以暴漏任意類(lèi)型的數(shù)據(jù),而不是像commonjs那樣exports只能為object),也應(yīng)采納。最終他們制定了一個(gè)Modules/Wrappings(http://wiki.commonjs.org/wiki/Modules/Wrappings)規(guī)范,此規(guī)范指出了一個(gè)模塊應(yīng)該如何“包裝”,包含以下內(nèi)容:

實(shí)際上這三個(gè)流派誰(shuí)都沒(méi)有勝過(guò)誰(shuí),反而是最后的AMD,CMD 規(guī)范扎根在這三個(gè)流派之上,吸取它們提出的優(yōu)點(diǎn)不斷得到壯大。
總的來(lái)說(shuō)AMD,CMD都是從commonJS規(guī)范中結(jié)合瀏覽器現(xiàn)實(shí)情況,并且吸收三大流派的優(yōu)點(diǎn)而誕生。其中CMD是國(guó)內(nèi)大牛制定的規(guī)范,其實(shí)現(xiàn)的工具是seaJS,而AMD則是國(guó)外大牛制定的,其實(shí)現(xiàn)技術(shù)則是requireJS

模塊化的優(yōu)點(diǎn)

既然我們已經(jīng)詳細(xì)的了解了“前端模塊化”的歷史與發(fā)展,那么我們也要大致了解模塊開(kāi)發(fā)的好處,畢竟這是我們學(xué)習(xí)的動(dòng)力。

1. 作用域污染
    小明定義了 var name = 'xiaoming';
    N ~ 天之后:
    小王又定義了一個(gè) var name = 'xiaowang';2.  防止代碼暴漏可被修改:
    為了解決全局變量的污染,早期的前端的先驅(qū)們則是以對(duì)象封裝的方式來(lái)寫(xiě)JS代碼:    var utils = {        'version':'1.3'
    };
    然而這種方式不可以避免的是對(duì)象中的屬性可被直接修改:utils.version = 2.0 。3. 維護(hù)成本的提升。
   如果代碼毫無(wú)模塊化可言,那么小明今天寫(xiě)的代碼,若干天再讓小明自己去看,恐怕也無(wú)從下手。4. 復(fù)用與效率
   模塊與非模塊的目的就是為了復(fù)用,提高效率

總的來(lái)說(shuō),前端的模塊化就是在眼瞎與手殘的過(guò)程進(jìn)行發(fā)展的,大致我們可以總結(jié)一下幾時(shí)代:

  1. 無(wú)序(洪荒時(shí)代) :自由的書(shū)寫(xiě)代碼。

  2. 函數(shù)時(shí)代 :將代碼關(guān)入了籠子之中。

  3. 面向?qū)ο蟮姆绞健?/p>

  4. 匿名自執(zhí)行函數(shù):其典型的代表作就是JQ。

  5. 偽模塊開(kāi)發(fā)(CMD/AMD)

  6. 模塊化開(kāi)發(fā)(還未誕生的ES6標(biāo)準(zhǔn))

我們相信未來(lái)必將更加光明,但是回顧現(xiàn)在,特別是在國(guó)內(nèi)的市場(chǎng)環(huán)境中IE瀏覽器依然占據(jù)半壁江山,所以基于ES6的模塊特性依然任重道遠(yuǎn),因此,在光明還未播撒的時(shí)刻,就讓我們率先點(diǎn)燃一朵火苗照亮自己,而這朵火苗就是 ———— requireJS

require 實(shí)戰(zhàn)

下面我將化整為零的去講解requireJS在一個(gè)項(xiàng)目的具體使用方式以及需要注意的事項(xiàng)。

引入requireJS

通過(guò) <script> 標(biāo)簽,將require.js 文件引入到當(dāng)前的 HTML 頁(yè)面中

<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>RequireJS 實(shí)戰(zhàn)</title></head><body>
    <script src="js/require.js"></script></body></html>

參數(shù)配置

requireJS 常用的方法與命令也就兩個(gè),因此requireJS使用起來(lái)非常簡(jiǎn)單。

  • require

  • define

其中define是用于定義模塊,而require是用于載入模塊以及載入配置文件。

在requireJS中一個(gè)文件就是一個(gè)模塊,并且文件名就是該模塊的ID,其表現(xiàn)則是以key/value的鍵值對(duì)格式,key即模塊的名稱(chēng)(模塊ID),而value則是文件(模塊)的地址,因此多個(gè)模塊便有多個(gè)鍵值對(duì)值,這些鍵值對(duì)再加上一些常用的參數(shù),便是require的配置參數(shù),這些配置參數(shù)我們通常會(huì)單獨(dú)保存在一個(gè)JS文件中,方便以后修改、調(diào)用,所以這個(gè)文件我們也稱(chēng)之為“配置文件”。

下面是requireJS的基本參數(shù)配置:

//index.html<script>require.config({
    baseUrl:'js/',
    paths:{        'jquery':'http://xxxx.xxx.com/js/jquery.min',        'index':'index'
    }
});require(['index']);</script>

require.config() 是用于配置參數(shù)的核心方法,它接收一個(gè)有固定格式與屬性的對(duì)象作為參數(shù),這個(gè)對(duì)象便是我們的配置對(duì)象。
在配置對(duì)象中 baseUrl 定義了基準(zhǔn)目錄,它會(huì)與 paths中模塊的地址自動(dòng)進(jìn)行拼接,構(gòu)成該模塊的實(shí)際地址,并且當(dāng)配置參數(shù)是通過(guò)script標(biāo)簽嵌入到html文件中時(shí),baseUrl默認(rèn)的指向路徑就是該html文件所處的地址。
paths 屬性的值也是一個(gè)對(duì)象,該對(duì)象保存的就是模塊key/value值。其中key便是模塊的名稱(chēng)與ID,一般使用文件名來(lái)命名,而value則是模塊的地址,在requireJS中,當(dāng)模塊是一個(gè)JS文件時(shí),是可以省略 .js 的擴(kuò)展名,比如 “index.js” 就可以直接寫(xiě)成 “index” 而當(dāng)定義的模塊不需要與 baseUrl 的值進(jìn)行拼接時(shí),可以通過(guò) "/" 與 http:// 以及 .js 的形式來(lái)繞過(guò) baseUrl的設(shè)定。
示例:

require.config({    baseUrl:'js/',
    paths:{        'jquery':'http://xxx.xxxx.com/js/jquery.min',        'index':'index'
    }
});require(['index']);

實(shí)際上,除了可以在require.js加載完畢后,通過(guò)require.config()方法去配置參數(shù),我們也可以在require.js加載之前,定義一個(gè)全局的對(duì)象變量 require 來(lái)事先定義配置參數(shù)。然后在require.js被瀏覽器加載完畢后,便會(huì)自動(dòng)繼承之前配置的參數(shù)。

<script>
    var require = {
        baseUrl: 'js/',
        paths: {            'jquery': 'http://xxx.xxxx.com/js/jquery.min',            'index': 'index'
        },
        deps:[index]
    };</script><script src="js/require.js"></script>

不論是在require.js加載之前定義配置參數(shù),還是之后來(lái)定義,這都是看看我們需求而言的,這里我們舉例的配置參數(shù)都是放入到script標(biāo)簽中,然后嵌入到HTML頁(yè)面的內(nèi)嵌方式,在實(shí)際使用時(shí),我們更多的則是將該段配置提取出來(lái)單獨(dú)保存在一個(gè)文件中,并將其取名為 app.js ,而這個(gè) app.js 便是我們后面常說(shuō)到的配置文件。

另外還有一個(gè)“接口文件”的概念,requireJS中,所謂接口文件指的便是require.js加載完畢后第一個(gè)加載的模塊文件。

加載配置文件

現(xiàn)在我們知道require的配置有兩種加載方式,一種是放入到script標(biāo)簽嵌入到html文件中,另一種則是作為配置文件 app.js 來(lái)獨(dú)立的引入。
獨(dú)立的引入配置文件也有兩種方式,一種是通過(guò)script標(biāo)簽加載外部JS文件形式:

<script src="js/require.js"></script><script src="js/app.js"></script>

另一種方式則是使用 require 提供的 data-main 屬性,該屬性是直接寫(xiě)在引入require.js的script標(biāo)簽上,在require.js 加載完畢時(shí),會(huì)自動(dòng)去加載配置文件 app.js。

html<script data-main="js/app" src="js/require.js"></script>

通過(guò) data-main 去加載入口文件,便會(huì)使配置對(duì)象中的 baseUrl 屬性默認(rèn)指向地址改為 app.js 所在的位置,相比之下我更加推薦這種方式,因?yàn)樗赡艿姆奖憧旖荨?/p>

當(dāng)我們的項(xiàng)目足夠的龐大時(shí),我也會(huì)推薦將入口文件作為一個(gè)普通的模塊,然后在這個(gè)模塊中,根據(jù)業(yè)務(wù)的不同再去加載不同的配置文件。

//define.jsdefine(['app1','app2','app3','app4'],function(app1,app2,app3,app4){    if(page == 'app1'){        require.config(app1);
    }else if(page == 'app2'){        require.config(app2);
    }else if(page == 'app3'){        require.config(app3);
    }else{        require.config(app4);
    }
})

當(dāng)然關(guān)于模塊的定義和載入我們后面會(huì)詳細(xì)的講解到,這里只需要有一個(gè)概念即可。

定義模塊

在我們選擇requireJS來(lái)模塊化開(kāi)發(fā)我們的項(xiàng)目或者頁(yè)面時(shí),就要明確的知道我們以后所編寫(xiě)的代碼或者是某段功能,都是要放在一個(gè)個(gè)定義好的模塊中。
下面是requireJS定義模塊的方法格式:

define([id,deps,] callback);

ID:模塊的ID,默認(rèn)的便是文件名,一般無(wú)需使用者自己手動(dòng)指定。
deps:當(dāng)前模塊所以依賴的模塊數(shù)組,數(shù)組的每個(gè)數(shù)組元素便是模塊名或者叫模塊ID。
callback:模塊的回調(diào)方法,用于保存模塊具體的功能與代碼,而這個(gè)回調(diào)函數(shù)又接收一個(gè)或者多個(gè)參數(shù),這些參數(shù)會(huì)與模塊數(shù)組的每個(gè)數(shù)組元素一一對(duì)應(yīng),即每個(gè)參數(shù)保存的是對(duì)應(yīng)模塊返回值。

根據(jù) define() 使用時(shí)參數(shù)數(shù)量的不同,可以定義以下幾種模塊類(lèi)型:

簡(jiǎn)單的值對(duì)

當(dāng)所要定義的模塊沒(méi)有任何依賴也不具有任何的功能,只是單純的返回一組鍵值對(duì)形式的數(shù)據(jù)時(shí),便可以直接將要返回的數(shù)據(jù)對(duì)象寫(xiě)在 define方法中:

define({    'color':'red',    'size':'13px',    'width':'100px'});

這種只為保存數(shù)據(jù)的模塊,我們稱(chēng)之為“值對(duì)”模塊,實(shí)際上值對(duì)模塊不僅可以用于保存數(shù)據(jù),還可以保存我們的配置參數(shù),然后在不同的業(yè)務(wù)場(chǎng)景下去加載不同的配置參數(shù)文件。

示例:

//app1.jsdefine({
    baseUrl:'music/js/',
    paths:{
        msuic:'music',
        play:'play'
    }
});
//app2.jsdefine({
    baseUrl:'video/js/',
    paths:{
        video:'video',
        play:'play'
    }
});

非依賴的函數(shù)式定義

如果一個(gè)模塊沒(méi)有任何的依賴,只是單純的執(zhí)行一些操作,那么便可以直接將函數(shù)寫(xiě)在 define方法中:

define(function(require,exports,modules){    // do something
    return {    'color':'red',    'size':'13px'
    }
});

依賴的函數(shù)式定義

這種帶有依賴的函數(shù)式模塊定義,也是我們平時(shí)常用到的,這里我們就結(jié)合實(shí)例,通過(guò)上面所舉的 index 模塊為例:

//index.jsdefine(['jquery','./utils'], function($) {
    $(function() {
        alert($);
    });
});

從上面的示例中我們可以看出 index 模塊中,依賴了 'jquery' 模塊,并且在模塊的回調(diào)函數(shù)中,通過(guò) $ 形參來(lái)接收 jquery模塊返回的值,除了 jquery 模塊,index模塊還依賴了 utils 模塊,因?yàn)樵撃K沒(méi)有在配置文件中定義,所以這里以附加路徑的形式單獨(dú)引入進(jìn)來(lái)的。

載入模塊

在說(shuō)載入模塊之前,我們先聊聊“模塊依賴”。模塊與模塊之間存在著相互依賴的關(guān)系,因此就決定了不同的加載順序,比如模塊A中使用到的一個(gè)函數(shù)是定義在模塊B中的,我們就可以說(shuō)模塊A依賴模塊B,同時(shí)也說(shuō)明了在載入模塊時(shí),其順序也是先模塊A,再模塊B。
在require中,我們可以通過(guò) require() 方法去載入模塊。其使用格式如下:

require(deps[,callback]);

deps:所要載入的模塊數(shù)組。
callback:模塊載入后執(zhí)行的回調(diào)方法。

這里就讓我們依然使用上述的 index 模塊為例來(lái)說(shuō)明
示例:

    require.config({        paths:{            'index':'index'
        }
    });    
    require(['index']);

requireJS 通過(guò) require([]) 方法去載入模塊,并執(zhí)行模塊中的回調(diào)函數(shù),其值是一個(gè)數(shù)組,數(shù)組中的元素便是要載入的模塊名稱(chēng)也就是模塊ID,這里我們通過(guò) require(['index']) 方法載入了 index 這個(gè)模塊,又因?yàn)樵撃K依賴了 jquery 模塊,所以接著便會(huì)繼續(xù)載入jquery模塊,當(dāng)jquery模塊加載完成后,便會(huì)將自身的方法傳遞給形參 $ 最后執(zhí)行模塊的回調(diào)方法,alert出$參數(shù)具體內(nèi)容。

這里我們可以小小的總結(jié)一下,實(shí)現(xiàn)模塊的載入除了 require([],fn) 的主動(dòng)載入方法,通過(guò)依賴也可以間接載入對(duì)應(yīng)的模塊,但是相比較而言require方式載入模塊在使用上更加靈活,它不僅可以只載入模塊不執(zhí)行回調(diào),也可以載入模塊然后執(zhí)行回調(diào),還可以在所定義的模塊中,按需載入所需要用到的模塊,并且將模塊返回的對(duì)象或方法中保存在一個(gè)變量中,以供使用。

這種按需載入模塊,也叫就近依賴模式,它的使用要遵循一定的使用場(chǎng)景:
當(dāng)模塊是非依賴的函數(shù)式時(shí),可以直接使用

define(function(require,exports,modules){
    var utils = require('utils');
    utils.sayHellow('hellow World')
})

當(dāng)模塊是具有依賴的函數(shù)式時(shí),只能夠以回調(diào)的形式處理。

define(['jquery'], function($) {
    $(function() {        require(['utils'],function(utils){
            utils.sayHellow('Hellow World!');
        });
    });
});

當(dāng)然聰明伶俐的你,一定會(huì)想到這樣更好的辦法:

define(['jquery','require','exports','modules'], function($,require,exports,modules) {
    $(function() {        //方式一
        require(['utils'],function(utils){
            utils.sayHellow('Hellow World!');
        });        //方式二:
        var utils = require('utils');
        utils.sayHellow('hellow World')
    });
});

模塊的返回值

require中定義的模塊不僅可以返回一個(gè)對(duì)象作為結(jié)果,還可以返回一個(gè)函數(shù)作為結(jié)果。實(shí)現(xiàn)模塊的返回值主要有兩種方法:

return 方式

// utils.jsdefine(function(require,exports,modules){    function sayHellow(params){
        alert(params);
    }    return sayHellow
});// index.jsdefine(function(require,exports,modules){    var sayHellow = require('utils');
    sayHellow('hellow World');
})

如果通過(guò)return 返回多種結(jié)果的情況下:

// utils.jsdefine(function(require,exports,modules){    function sayHellow(params){
        alert(params);
    }    
    function sayBye(){
        alert('bye-bye!');
    }    
    return {        'sayHellow':sayHellow,        'sayBye':sayBye
    }
});// index.jsdefine(function(require,exports,modules){    var utils = require('utils');
    utils.sayHellow('hellow World');
})

exports導(dǎo)出

// utils.jsdefine(function(require,exports,modules){    function sayHellow(params){
        alert(params);
    }
    exports.sayHellow = sayHellow;
})// index.jsdefine(function(require,exports,modules){    var utils = require('utils');
    utils.sayHellow('hellow World');
});

這里有一個(gè)注意的地方,那就是非依賴性的模塊,可以直接在模塊的回調(diào)函數(shù)中,加入以下三個(gè)參數(shù):

require:加載模塊時(shí)使用。
exports:導(dǎo)出模塊的返回值。
modules:定義模塊的相關(guān)信息以及參數(shù)。http://www.cnblogs.com/HCJJ/p/6611669.html

https://segmentfault.com/a/1190000002401665 //對(duì)require的配置參數(shù)講解的很詳細(xì)