AMD及requireJS

來源:互聯網
上載者:User

標籤:多級   報錯   進度   產生   git   app   depend   其他   模式   

前面的話

  由CommonJS組織提出了許多新的JavaScript架構方案和標準,希望能為前端開發提供統一的指引。AMD規範就是其中比較著名一個,全稱是Asynchronous Module Definition,即非同步模組載入機制。完整描述了模組的定義,依賴關係,參考關聯性以及載入機制。而AMD規範的作者親自實現了符合AMD規範的requireJS。本文將詳細介紹AMD及requireJS

 

AMD規範

  AMD(Asynchronous Module Definition)翻譯為非同步模組定義。非同步強調的是,在載入模組以及模組所依賴的其它模組時,都採用非同步載入的方式,避免模組載入阻塞了網頁的渲染進度

  AMD作為一個規範,只需定義其文法API,而不關心其實現。AMD規範簡單到只有一個API,即define函數

define([module-name?], [array-of-dependencies?], [module-factory-or-object]);

  module-name: 模組標識,可以省略

  array-of-dependencies: 所依賴的模組,可以省略

  module-factory-or-object: 模組的實現,或者一個JavaScript對象

  define函數具有非同步性。當define函數執行時,首先會非同步去調用第二個參數中列出的相依模組,當所有的模組被載入完成之後,如果第三個參數是一個回呼函數則執行;然後告訴系統模組可用,也就通知了依賴於自己的模組自己已經可用

 

載入

  使用require.js的第一步,是先去官方網站下載最新版本。下載後,假定把它放在js子目錄下面,就可以載入了

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

  HTML中的script標籤在載入和執行過程中會阻塞網頁的渲染,所以一般要求盡量將script標籤放置在body元素的底部,以便加快頁面顯示的速度,還有一種方式就是通過非同步載入的方式來載入js檔案,這樣可以避免js檔案對html渲染的阻塞

<script src="js/require.js" defer async></script>

 

入口檔案

  require.js在載入的時候會檢查data-main屬性,當requireJS自身載入執行後,就會再次非同步載入data-main屬性指向的main.js。這個main.js是當前網頁所有邏輯的入口,理想情況下,整個網頁只需要這一個script標記,利用requireJS載入依賴的其它檔案

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

  [注意]在main.js中所設定的指令碼是非同步載入的。所以如果在頁面中配置了其它JS載入,則不能保證它們所依賴的JS已經載入成功

<script data-main="scripts/main" src="js/require.js"></script><script src="js/other.js"></script>

【內部機制】

  在RequireJS內部,會使用head.appendChild()將每一個模組依賴載入為一個script標籤。RequireJS等待所有的依賴載入完畢,計算出模組定義函數正確調用順序,然後依次調用它們

 

模組

  模組不同於傳統的指令檔,它良好地定義了一個範圍來避免全域名稱空間汙染。它可以顯式地列出其依賴關係,並以函數(定義此模組的那個函數)參數的形式將這些依賴進行注入,而無需引用全域變數。RequireJS的模組是模組模式的一個擴充,其好處是無需全域地引用其他模組

  RequireJS的模組文法允許它儘快地載入多個模組,雖然載入的順序不定,但依賴的順序最終是正確的。同時因為無需建立全域變數,甚至可以做到在同一個頁面上同時載入同一模組的不同版本

  一個檔案應該只定義1個模組。多個模組可以使用內建最佳化工具將其組織打包

  如果我們的代碼不依賴任何其他模組,那麼可以直接寫入javascript代碼

//main.js
console.log(1);

  但這樣的話,就沒必要使用require.js了。真正常見的情況是,主模組依賴於其他模組,這時就要使用AMD規範定義的的require()函數

// main.jsrequire([‘moduleA‘], function(a){  console.log(a);});//moduleA.jsdefine(function(){    return 1;})

  這裡拋出一個問題,為什麼主模組使用的是require()函數,而模組moduleA使用define()函數呢?因為define()定義的模組可以被調用,而require()不可以。主模組main.js是入口檔案,需要調用別的模組,而不需要被別的模組調用,所以使用require()或define()都可以。而moduleA需要被調用,所以只能使用define()

  如果把moduleA.js中的define()方法改為require()方法,則返回undefined

// main.jsrequire([‘moduleA‘], function(a){  console.log(a);});//moduleA.jsrequire(function(){    return 1;})

【簡單的值對】

  上面的模組moduleA中,回呼函數返回了一個數字。而實際上,模組可以有多種形式,比如一個簡單的值對

define({    color: "black",    size: "unisize"});

  返回的結果如下:

【函數式定義】

  如果一個模組沒有任何依賴,但需要一個做setup工作的函數,則在define()中定義該函數,並將其傳給define()

define(function () {    //Do setup work here    return {        color: "black",        size: "unisize"    }});

  返回的結果如下:

【存在依賴的函數式定義】

  如果模組存在依賴:則第一個參數是依賴的名稱數組;第二個參數是函數,在模組的所有依賴載入完畢後,該函數會被調用來定義該模組,因此該模組應該返回一個定義了本模組的object。依賴關係會以參數的形式注入到該函數上,參數列表與依賴名稱列表一一對應

//moduleA.jsdefine([‘moduleB‘], function(b) {    var num = 10;    return b.add(num);    });////moduleB.jsdefine({    add: function(n){        return n+1;    }});

【命名模組】

  define()中可以包含一個模組名稱作為首個參數

//moduleA.jsdefine("moduleA",[‘moduleB‘], function(b) {    var num = 10;    return b.add(num);    });

  這些常由最佳化工具產生。也可以自己顯式指定模組名稱,但這使模組更不具備移植性——就是說若將檔案移動到其他目錄下,就得重新命名。一般最好避免對模組寫入程式碼,而是交給最佳化工具去產生。最佳化工具需要產生模組名以將多個模組打成一個包,加快到瀏覽器的載入速度

 

路徑配置

  html中的base元素用於指定文檔裡所有相對URL地址的基礎URL,requireJS的baseUrl跟這個base元素起的作用是類似的,由於requireJS總是動態地請求依賴的JS檔案,所以必然涉及到一個JS檔案的路徑解析問題,requireJS預設採用一種baseUrl + moduleID的解析方式,requireJS對它的處理遵循如下規則:

  1、在沒有使用data-main和config的情況下,baseUrl預設為當前頁面的目錄

  2、在有data-main的情況下,main.js前面的部分就是baseUrl,比如上面的js/

  3、在有config的情況下,baseUrl以config配置的為準

  上述三種方式,優先順序由低到高排列

  RequireJS以一個相對於baseUrl的地址來載入所有的代碼。頁面頂層script標籤含有一個特殊的屬性data-main,require.js使用它來啟動指令碼載入過程,而baseUrl一般設定到與該屬性相一致的目錄

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

  在模組章節的樣本中,代碼如下所示

// main.jsrequire([‘moduleA‘], function(a){  console.log(a);});//moduleA.jsdefine(function(){    return 1;})

  入口檔案main.js依賴於moduleA,直接寫成[‘moduleA‘],預設情況下,require.js假定moduleA與main.js在同一個目錄,即‘js/moduleA.js‘,檔案名稱為moduleA.js,然後自動載入

  使用require.config()方法,我們可以對模組的載入行為進行自訂。require.config()就寫在主模組(main.js)的頭部。參數就是一個對象,這個對象的paths屬性指定各個模組的載入路徑

  下面在demo檔案夾下建立一個test檔案夾,並在test檔案夾下建立一個moduleA.js檔案,內容如下

//moduleA.jsdefine(function(){    return 2;})

  而在原來的js檔案夾下,依然存在一個moduleA.js檔案,內容如下

//moduleA.jsdefine(function() {    return 1;});

  當js檔案夾下的main.js進行config配置時

// main.jsrequire.config({    baseUrl: ‘test‘})require([‘moduleA‘], function(a){    console.log(a);});

   結果為2,說明識別的是‘test/moduleA.js‘檔案

  當js檔案夾下的main.js不進行config配置時

// main.jsrequire([‘moduleA‘], function(a){    console.log(a);});

  結果為1,說明識別的是‘js/moduleA.js‘檔案

  RequireJS預設假定所有的依賴資源都是js指令碼,因此無需在module ID上再加".js"尾碼,RequireJS在進行module ID到path的解析時會自動補上尾碼 

  如果一個模組的路徑比較深,或者檔案名稱特別長,比如‘js/lib/moduleA.min.js‘,則可以使用config設定物件中的paths屬性

// main.jsrequire.config({    paths:{        ‘moduleA‘:‘lib/moduleA.min‘    }})require([‘moduleA‘], function(a){    console.log(a);});//moduleA-min.jsdefine(function(){    return 3;})

  結果為3

  要注意的是,這裡的paths的‘moduleA‘設定的是‘lib/moduleA.min‘,而不是‘js/lib/moduleA.min‘,是因為requireJS中的檔案解析是一個"baseUrl + paths"的解析過程

  在index.html的入口檔案設定的是‘js/main‘,所以baseURL是‘js‘。因此‘baseUrl + paths‘ = ‘js/lib/moduleA.min‘

<script src="require.js" data-main="js/main" defer async></script>

  如果在config設定物件中設定了baseUrl,則以此為準

// main.jsrequire.config({    baseUrl: ‘js/lib‘,    paths:{        ‘moduleA‘:‘moduleA.min‘    }})require([‘moduleA‘], function(a){    console.log(a);});

  結果同樣為3,baseURL是‘js/lib‘,paths是‘moduleA.min‘。因此‘baseUrl + paths‘ = ‘js/lib/moduleA.min‘

  如果一個module ID符合下述規則之一,其ID解析會避開常規的"baseUrl + paths"配置,而是直接將其載入為一個相對於當前HTML文檔的指令碼:1、以 ".js" 結束;2、包含 URL 協議,如 "http:" or "https:"

  如下所示,require()函數所依賴的模組路徑為‘js/moduleA.js‘

// main.jsrequire.config({    baseUrl: ‘js/lib‘,    paths:{        ‘moduleA‘:‘moduleA.min‘    }})require([‘js/moduleA.js‘], function(a){    console.log(a);});

  而該檔案的代碼如下,路徑為‘js/moduleA.js‘,而不是‘js/lib/moduleA.min‘,所以,最終結果為1

//moduleA.jsdefine(function() {    return 1;});

  一般來說,最好還是使用baseUrl及"paths" config去設定module ID。它會帶來額外的靈活性,如便於指令碼的重新命名、重定位等。 同時,為了避免淩亂的配置,最好不要使用多級嵌套的目錄層次來組織代碼,而是要麼將所有的指令碼都放置到baseUrl中,要麼分置為項目庫/第三方庫的一個扁平結構,如下

www/    index.html    js/        app/            sub.js        lib/            jquery.js            canvas.js        main.js

 

CommonJS

  前面提到過,commonJS主要應用於伺服器端編程,如nodejs。使用打包工具Browserify可以對CommonJS進行格式轉換,使其可以在瀏覽器端進行

  而requireJS支援一種簡單封裝CommonJS的方式,只要在commonJS代碼的外層簡單包裹一層函數,就可以在瀏覽器端直接運行

define(function(require, exports, module) {});

  如果該模組還依賴其他模組,如相依模組moduleA,則代碼如下

define([‘moduleA‘],function(require, exports, module) {});

  a.js和b.js的commonJS形式的代碼如下

// a.jsvar a = 100;module.exports.a = a;// b.jsvar result = require(‘./a‘);console.log(result.a);

  index.html直接引用b.js會報錯,提示require沒有被定義

<script src="b.js"></script> 

  將a.js和b.js進行改造之後,代碼如下

// a.jsdefine(function(require, exports, module) {    var a = 100;    module.exports.a = a;});// b.jsdefine(function(require, exports, module) {    var result = require(‘./a‘);    console.log(result.a);});

  index.html將入口檔案設定為‘js/b‘,則結果為100

<script src="require.js" data-main="js/b" defer async></script>

 

懶載入

  有如下例子,入口檔案main.js代碼如下

// main.jsrequire([‘a‘], function(a){    console.log(‘main‘);    document.onclick = function(){        a.test();    }});

  所依賴的模組a.js的代碼如下

define(function(){    console.log(‘a‘);    return {        test : function(){            console.log(‘a.test‘);        }    }})

  在瀏覽器端執行時,即使不點擊頁面,瀏覽器也會下載a.js檔案。這個效能消耗是不容忽視的

  AMD保留了commonjs中的require、exprots、module這三個功能。可以不把依賴羅列在dependencies數組中。而是在代碼中用require來引入

  重寫後的代碼如下

// main.jsdefine(function(){    console.log(‘main‘);    document.onclick = function(){        require([‘a‘],function(a){            a.test();        });    }});//a.jsdefine(function(){
  console.log(‘a‘); return { test : function(){ console.log(‘a.test‘); } }})

  在瀏覽器端執行時,如果不點擊頁面,瀏覽器就不會下載a.js檔案,這樣就實現懶載入

 

其他配置

  在requireJS中,除了路徑配置之外,還有一些其他配置

【配置設定】

  在前面的例子中,我們配置requireJS中的路徑是通過入口檔案main.js中的config對象來配置的。實際上,不通過入口檔案,也可以進行requireJS的配置

  1、在index.html檔案嵌入javascript代碼

  在HTML檔案中,載入requireJS檔案之後,立即對requireJS進行配置,相當於將main.js檔案變為內嵌的javascript檔案

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Document</title></head><body><script src="require.js"></script><script>require.config({    baseUrl: ‘js/lib‘,    paths:{        ‘moduleA‘:‘moduleA.min‘    }})    require([‘moduleA‘], function(a){    console.log(a);});</script></body></html>

  2、將配置作為全域變數"require"在require.js載入之前進行定義,它會被自動應用

  這裡有一個問題是,如果require作為全域變數被提前定義,則data-main入口檔案,是以baseUrl為基礎進行設定的

  [注意]使用 var require = {} 的形式而不是 window.require = {}的形式。後者在IE中運行不正常

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Document</title></head><body><script>var require = {    baseUrl: ‘js/lib‘,    paths:{        ‘moduleA‘:‘moduleA.min‘    }    }    </script><script src="require.js" data-main="../main"></script></body></html>

【shim】

  shim屬性為那些沒有使用define()來聲明依賴關係、設定模組的"瀏覽器全域變數注入"型指令碼做依賴和匯出配置,即載入非規範的模組

  舉例來說,underscore和backbone這兩個庫,都沒有採用AMD規範編寫。如果要載入它們的話,必須先定義它們的特徵。具體來說,每個模組要定義(1)exports值(輸出的變數名),表明這個模組外部調用時的名稱;(2)deps數組,表明該模組的依賴性

  通過如下配置後,現在可以通過_調用underscore的api,使用Backbone來調用backbone的api

  require.config({    shim: {      ‘underscore‘:{        exports: ‘_‘      },      ‘backbone‘: {        deps: [‘underscore‘, ‘jquery‘],        exports: ‘Backbone‘      }    }  });

  jQuery的外掛程式可以如下這樣定義,現在可以通過jQuery.fn.scroll來調用該外掛程式的api

  shim: {    ‘jquery.scroll‘: {      deps: [‘jquery‘],      exports: ‘jQuery.fn.scroll‘    }  }

 

外掛程式

  require.js還提供一系列外掛程式,實現一些特定的功能

【dom ready】 

  RequireJS載入模組速度很快,很有可能在頁面DOM Ready之前指令碼已經載入完畢。需要與DOM互動的工作應等待DOM Ready。現代的瀏覽器通過DOMContentLoaded事件來知會

  但是,不是所有的瀏覽器都支援DOMContentLoaded。domReady模組實現了一個跨瀏覽器的方法來判定何時DOM已經ready

// main.jsrequire([‘domready!‘], function(){    console.log(‘ready‘);});

【text】

  text外掛程式可以用來載入如.html、.css等文字檔,可以通過該外掛程式來實現完整組件(結構+邏輯+樣式)的組件化開發

require(["some/module", "text!some/module.html", "text!some/module.css"],    function(module, html, css) {    });

 

AMD及requireJS

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.