后臺小白前端入門--RequireJS

導語:

之前一直有聽說RequireJS,但是一直都沒機會去了解,只知道它是一個給js做模塊化的API。最近在做React,其組件化的思想和js模塊化的思想不謀而合。就想在項目中應用React的同時,也把RequireJS加進來,看看會不會對頁面加載或者開發有很好的效果。

What is RequireJS?

在說明什么是RequireJS之前,不得不提的就是Javascript模塊化歷史的背景。其實在早期,javascript作為一門新興的腳本語言出現,有著龐大的愿景,它并不是作為一門僅僅針對客戶端設計的語言。只是說后來web應用的流行,javascript作為瀏覽器端腳本語言而迅速傳開,加上Netscape和微軟的競爭將其過早的標準化。所以就導致了JS的諸多缺陷,其中一個就是模塊化(但是你可以驚奇地發現其實javascript有將import,export等作為保留字,說明設計的時候其實是有考慮的,新的標準es6也讓原生支持模塊化了)。然后隨著web應用越來越復雜,嵌入的javascript代碼越來越多,還有node的興起,模塊化編程就變成了必須。

所以就有了后來Dojo工具包和Google的Closure庫支持的模塊系統。還有兩個非常通用的標準規范,CommonJS和AMD。這里就不展開說了,我們只需要知道,實現CommonJS規范的API是同步加載模塊的,而實現AMD規范的API是則是異步加載模塊。
所以理論上來說,AMD規范的非阻塞加載更加適合瀏覽器端。而RequireJS就是AMD規范的最好實現。抄一段官方文檔對RequireJS的描述:

RequireJS 是一個JavaScript模塊加載器。它非常適合在瀏覽器中使用, 它非常適合在瀏覽器中使用,但它也可以用在其他腳本環境, 就像 Rhino and Node. 使用RequireJS加載模塊化腳本將提高代碼的加載速度和質量。

Why RequireJS?

所以,知道了RequireJS是干什么的,也差不多知道為什么我們要使用RequireJS了。不過還是總結一下用RequireJS的好處吧。

  • 異步“加載”。我們知道,通常網站都會把script腳本的放在html的最后,這樣就可以避免瀏覽器執行js帶來的頁面阻塞。使用RequireJS,會在相關的js加載后執行回調函數,這個過程是異步的,所以它不會阻塞頁面。
  • 按需加載。通過RequireJS,你可以在需要加載js邏輯的時候再加載對應 的js模塊,這樣避免了在初始化網頁的時候發生大量的請求和數據傳輸,或許對于一些人來說,某些模塊可能他根本就不需要,那就顯得沒有必要。
  • 更加方便的模塊依賴管理。相信你曾經一定遇到過因為script標簽順序問題而導致依賴關系發生錯誤,這個函數未定義,那個變量undefine之類的。通過RequireJS的機制,你能確保在所有的依賴模塊都加載以后再執行相關的文件,所以可以起到依賴管理的作用。
  • 更加高效的版本管理。想一想,如果你還是用的script腳本引入的方式來引入一個jQuery2.x的文件,然后你有100個頁面都是這么引用的,那當你想換成jQuery3.x,那你就不得不去改這100個頁面。但是如果你的requireJS有在config中做jQuery的path映射,那你只需要改一處地方即可。
  • 當然還有一些諸如cdn加載不到js文件,可以請求本地文件等其它的優點,這里就不一一列舉了。

RequireJS 使用

需要在頁面中引入的文件

 <script data-main="js/main" src="xxx/xxxx/require.js"></script>

使用RequireJS,你只需要引入一個require.js即可。需要說明的是,一個比較好的實踐,就是你的頁面上面應該也只需要通過<script>標簽引入這一個js即可。然后你這個頁面的所有業務邏輯只需要在main.js里面寫(data-main屬性作用后面會有講)就可以了。其它引用的依賴怎么辦?當然是通過require按需引入啊!

Require基本概述

其實Requirejs整個源文件包括注釋就2000來行,其對外暴露的變量其實就三個,requirejs,require,define

這其中requirejs 只是require的一個別名,目的是如果頁面中有require其它實現了,你還是能通過使用requirejs來使用requireJS API的(本文中沒有相關沖突,所以還是使用require)。

所以這意味著作為入門,你只需要掌握require,require.config,define這三樣就可以了。

本文將以介紹require,require.config,data-main,define的順序來介紹RequireJS。讓比較簡單的RequireJS更加簡單,爭取讓大家只看這篇文章就能用好RequireJS。至于RequireJS是如何解決循環依賴,對于沒有實現amd的模塊如何通過shim來導出,如何在node中使用等問題。本文并沒有提及,詳細有需要可以去官方查閱。

require

首先,先不管三七二十一,我們先按照下面的方式創建一個這樣的目錄結構:

圖片名稱

然后require.js可以通過npm下載或者在官網獲得。jquery同理,jquery需要下載1.7.0或以上的版本。然后把對應的代碼拷入對應的文件中,給出余下兩個文件的代碼:

//  js/script/index.html
<!DOCTYPE html>
<html>
<head>
    <title>Require Demo 1</title>
</head>
<body>
    <div>
        <h1>Require Demo 1 -- usage of Require()</h1>
        <button id="contentBtn">Click me</button>
    <p id="messagebox"></p>
    </div>
    <script data-main="js/script/main" src="js/lib/require.js" type="text/javascript"></script> 
</body>
</html>      
      
//  js/script/main.js
    require.config(
        {
            paths: {
                'jquery': '../lib/jquery-1.7.2'
            }
        }
    );
    require(['jquery'],function ($) {
             $(document).on('click','#contentBtn',function(){
                $('#messagebox').html('You have access Jquery by using require()');
             });
    });
      

先看index.html的代碼,其實比較簡單,頁面上在js中會用到的就是一個button和一個p標簽。然后整個頁面就只是一個js文件是通過<script>標簽加載的,就是require.js。注意到標簽中有一個data-main屬性,你現在只需要了解require.js會在加載完成以后通過回調方法去加載這個data-main里面的js文件,所以這個js文件被加載的時候,RequireJS已經加載執行完畢。

然后接著看main.js文件,里面被一個匿名立即執行函數所包括。在require.config(...)中,可以配置許多配置項,后面會有詳細說明。上面在config中添加了一個path,在path配置了一個模塊ID和路徑的映射,這樣在后續的所有函數中就可以直接通過模塊ID來引入依賴,而不用再多次引入依賴多次輸入路徑帶來的麻煩。

然后接著就是我們的require(...)函數了。上面的語法中require函數接受的第一個參數是,所依賴模塊的一個數組。即使你只需要傳入一個依賴,也需要把這個依賴放進數組中傳入。如果你有如本例子中設置了模塊ID和路徑的映射,那你在傳入依賴的時候就可以使用模塊ID來代替路徑,如果沒有配置模塊ID你當然也可以通過路徑來引進對應的模塊。接著是傳入回調函數,當引入的依賴加載完畢后,這個回調函數就會被觸發。如果你傳入的依賴有注入變量(函數),然后在回調函數中需要用到,你就需要按照順序在回調函數的參數中添加別名,在本例子中可以通過別名$來使用jQuery的相關API。所以有注入的模塊需要放在無注入或者不需要調用模塊的模塊前面,方便回調函數傳入別名。例子中在回調函數中為id為contentBtn的button注冊監聽事件,如果觸發,則往id為messagebox的p標簽添加相應的內容。

另外還需要額外說明的是路徑,不管是在配置中寫路徑還是直接在require函數中寫路徑,你都需要了解requireJS在不同情況下的相對路徑。

以下是相對路徑的規則:

1.如果<script>標簽引入require.js時沒有指定data-main屬性,則以引入該js的html文件所在的路徑為根路徑。

2.如果有指定data-main屬性,也就是有指定入口文件,則以入口文件所在的路徑為根路徑。在本例子中也就是main.js所在的script文件夾就是根路徑,這也是為什么配置jQuery的時候需要返回上層目錄再進入lib目錄才能找到jQuery文件。

3.如果再require.config()中有配置baseUrl,則以baseUrl的路徑為根路徑。
以上三條優先級逐級提升,如果有重疊,后面的根路徑覆蓋前面的根路徑。

打開網頁,然后你就應該看到這樣的頁面:

圖片名稱

點擊按鈕,有如下效果,說明通過RequireJS已載入Jquery,并且通過Jquery綁定了監聽事件。

圖片名稱

define

講完了如何引入模塊,現在講如何定義一個模塊,require定義一個模塊是通過 define = function (name, deps, callback)完成的,第一個參數是定義模塊名,第二個參數是傳入定義模塊所需要的依賴,第三個函數則是定義模塊的主函數,主函數和require的回調函數一樣,同樣是在依賴加載完以后再調用執行。
先看個例子:

當你沒有任何依賴的時候,你可以這么寫:
//   js/script/desc.js
define(function(){
    return{
        decs : 'this js will be request only if it is needed',
    };
})
      
//   然后在main.js的添加如下代碼
//   js/script/main.js
    $('#messagebox').html('You have access Jquery by using require()');
+  require(['script/desc'],function(desc){
+       alert(JSON.stringify(desc));

再次打開網頁,打開network視圖,點擊按鈕,通過require獲得的desc模塊就會alert出來,同時你會發現,desc.js是按需請求的,并不是在頁面一開始的時候就請求的。

圖片名稱
當你有相關依賴的時候,你可以這么寫:
//   js/script/alertdesc.js
define(['script/desc'],function(desc){
    return function (){
        alert(JSON.stringify(desc));
    };
})
      
//   然后在main.js的再做如下修改
//   js/script/main.js
    $('#messagebox').html('You have access Jquery by using require()');
-  require(['script/desc'],function(desc){
-       alert(JSON.stringify(desc));
+   require(['script/alertdesc'],function(alertdesc){
+      alertdesc();

為什么我始終都沒有使用name來定義自己的模塊名:

如果你細心,你可能會發現,剛剛define函數,有一個參數name是用來定義模塊名的(也就是第一個傳參),為什么上面兩個例子都沒有用到。其實我確實可以添加模塊名,如下:

//   js/script/alertdesc.js
define(['script/desc'],function(desc){
   .....
})

//Change To

define('/script/alertdesc',['script/desc'],function(desc){
   .....
})
      

但是,這樣做感覺不很有必要,因為如果哪一天我將這個文件轉移到其他目錄下,那我就得在這這里再修改一次模塊名。官方其實也不推薦,用官方的說法是:讓優化工具去自動生成這些模塊名吧!

require.config

在上面一節介紹require()函數的時候,我們已經接觸過require.config(...)了。其實說白了,在require.config()做的一些修改會影響到全局require的一些特性。如上面的,你設置了baseUrl
,其require的根路徑就以這個路徑為準,你在path中設置了模塊ID與路徑的映射,后面需要用到相關模塊的時候直接使用模塊ID代替路徑就好了,設置map可以在不同路徑下用相同的模塊ID調用不同版本的模塊。

其實這里并不打算對require.config()的具體配置展開來介紹,有需要可以直接去官網查閱相關配置信息加進來就好了。
始終覺得require.config()應該抽出來,單獨放在一個js文件里面,這樣方便移植和重用。在github上看了些例子,找到一個比較好的放置require.config的地方,放在這里可以參考:

//  添加config.js
//  js/script/config.js
define(function(){
    require.config({
        baseUrl: './js/',
        paths: {
            'jquery': 'lib/jquery-1.7.2'
        }
    });
});      
//  替換main.js
//  js/script/main.js
require(['config'],function(){
    require(['jquery'],function ($) {
         $(document).on('click','#contentBtn',function(){
            $('#messagebox').html('You have access Jquery by using require()');
            require(['script/alertdesc'],function(alertdesc){
                alertdesc();
            });
         });
    });
});
      

data-main

還記得剛剛的<script>引入RequireJS時標簽中有一個data-main屬性么?當require.js加載的時候會檢查data-main屬性,所以你可以在data-main指向的腳本(也就是本例子中的js/main.js)中設置模塊加載的選項,然后在這個腳本加載第一個應用模塊。注意,你在main.js中所設置的腳本是異步加載并通過回調來執行的,這意味著如果你在頁面中有通過<script>引入其它的腳本,那不能保證在main.js里面做的配置會在其它腳本中生效。
例如:

 <script data-main="scripts/main" src="scripts/require.js"></script>
 <script src="scripts/other.js"></script>
//  scripts/main.js:
require.config({
    paths: {
        foo: 'libs/foo-1.1.3'
    }
});
// scripts/other.js:

// 由于main.js會是在require.js異步加載完以后再通過回調去執行main.js的
// 所以other.js里面執行的這個require函數可能會發生在main.js的require.config執行之前
// 因此require.config會去嘗試去加載"scripts/foo.js",而不是"scripts/libs/foo-1.1.3.js"
require( ['foo'], function( foo ) {

});

這個例子是官方的。從這里也可以看出來,為什么如前文所說的。頁面中最好只有一個入口點文件(屬性data-main中引入的main.js),然后這個入口點文件里引入或者編寫配置,加載相關應用模塊。
當然你也可以像官方給的第二種方案,不設置入口點,然后在每個require回調中再引入相關配置,不過那樣很麻煩而且不易于維護。這里就不給出例子了,有需要可以去官網看。

總結

以上就是關于關于RequireJS簡單使用的介紹了,大家有需要可以直接看源碼,大概就2000多行,不看具體實現,看它對幾個函數聲明的描述,對使用起來也是很有幫助的,你會發現有一些連官方文檔都沒提及到的一些特性(比如require()方法可以直接傳入config配置作為第一個參數)。

另外,說一點小插曲,如果需要查閱RequireJS官方的API,有條件的還是建議直接訪問英文官方文檔。如果說中文的官方文檔說還停留在老版本,翻譯得比較生澀難懂就算了。一些很明顯有錯誤的描述就真的是責任問題了。我在看中文文檔的時候真是各種難移理解,后來直接看英文文檔,則順暢很多。不多說,貼張圖讓大家感受一下英文文檔和中文文檔對于waitSeconds的描述:

圖片名稱

參考資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容