天天看點

requirejs入門到精通

更多内容檢視API:http://www.requirejs.cn/

使用了塊作用域來申明function防止污染全局變量

聲明不同js檔案之間的依賴

可以按需、并行、延時載入js庫

可以讓我們的代碼以子產品化的方式組織

通常使用requirejs的話,我們隻需要導入requirejs即可,不需要顯式導入其它的js庫,因為這個工作會交給requirejs來做。

data-main

 是告訴requirejs:你下載下傳完以後,馬上去載入真正的入口檔案。它一般用來對requirejs進行配置,并且載入真正的程式子產品。

<script src="/path/to/require.js" data-main="/path/to/app/config.js"></script>           
requirejs下。其主要API主要是下面三個函數:

       
  • define– 該函數使用者建立子產品。每個子產品擁有一個唯一的子產品ID,它被用于RequireJS的運作時函數,define函數是一個全局函數,不需要使用requirejs命名空間.
  • require– 該函數用于讀取依賴。同樣它是一個全局函數,不需要使用requirejs命名空間.
  • config– 該函數用于配置RequireJS.

require.config配置參數選項

  • baseUrl——用于加載子產品的根路徑。
  • paths——用于映射不存在根路徑下面的子產品路徑。
  • shims——配置在腳本/子產品外面并沒有使用RequireJS的函數依賴并且初始化函數。假設underscore并沒有使用  RequireJS定義,但是你還是想通過RequireJS來使用它,那麼你就需要在配置中把它定義為一個shim。
  • deps——加載依賴關系數組
config.js                 中通常用來做兩件事:      
  1. 配置requirejs 比如項目中用到哪些子產品,檔案路徑是什麼
  2. 載入程式主子產品

requirejs一共提供了兩個全局變量:

  1. requirejs/require: 用來配置requirejs及載入入口子產品。如果其中一個命名被其它庫使用了,我們可以用另一個
  2. define: 定義一個子產品

另外還可以把 

require

 當作依賴的子產品,然後調用它的方法:

define(["require"], function(require) {
    var cssUrl = require.toUrl("./style.css");
});                

依賴一個不使用requirejs方式的庫

如果沒用

define(...)

 定義子產品,比如這個 

hello.js

,按普通方式定義了一個函數,能在requirejs裡使用它嗎?

function hello() {
  alert("hello, world~");
}
           

先看下面不能正确工作的代碼:

requirejs.config({
  baseUrl: '/public/js',
  paths: {
    hello: 'hello'
  }
});

requirejs(['hello'], function(hello) {
  hello();
});
           

這段代碼會報錯,提示:   Uncaught TypeError: undefinedisnot a function

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);
});
           

它會提示我:jqisundefined

但我僅僅改個名字:

requirejs.config({
  baseUrl: '/public/js',
  paths: {
    jquery: 'lib/jquery/jquery'            
"jquery" : ["http://libs.baidu.com/jquery/2.0.3/jquery", "js/jquery"],   /// 這樣配置後,當百度的jquery沒有加載成功後,會加載本地js目錄下的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

 子產品。但是使用後者的話,全局變量 

$

 可能又重新被污染了。

但是如果我不使用shim這個參數的話,在最新版的requirejs2.1.15中(以前的版本我不太清楚),也可以通過require([‘XX’])來解決。

使用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

 的依賴,使得它最先被執行。

Map參數: Map參數是用來解決同一個子產品不同版本的問題,比如在項目開發中,開發初期使用了jquery1.7版本,但是由于業務的需求需要引入jquery1.9以上的版本時候,但是又擔心有些是依賴于jquery1.7的代碼更新到1.9以上的時候會有問題,是以可以讓一部分代碼還是依賴于jquery1.7,薪增的代碼依賴于jquery1.9.

現在我在入口檔案app.js添加如下代碼:

requirejs.config({

map: {

'app/a': {

'jquery': 'js/lib/jquery1.7.js'

},

'app/b': {

'jquery': 'js/lib/jquery1.9.1.js'

}

}

});

require(['app/a'],function(jq){   

});

require(['app/b'],function(jq){  

});

然後在app/a.js添加如下代碼:

// a.js

define(function (require, exports, module) {

var a = require(['jquery']);

});

在app/b.js添加如下代碼:

// b.js

define(function (require, exports, module) {

var b = require(['jquery']);

});

在app.js中

require(['app/a'],function(jq){  

});時候,在加載app/a.js的時候會加載jquery1.7.js檔案,在加載app/b.js的時候會加載jquery1.9.1.js.如下截圖所示:

requirejs入門到精通

如果在app.js中把下面這行b.js代碼初始化注釋掉

require(['app/b'],function(jq){   

});

那麼就隻會加載app/a.js及對應的jquery1.7.js,截圖如下:

requirejs入門到精通

相應的 如果把app/a.js初始化代碼注釋掉,把app/b.js代碼初始化打開,那麼隻會加載jquery1.9.1,可以看到如果我想app/b.js中使用jquery1.9的話,那麼可以這樣使用了。

config參數:  config是指需要将配置資訊傳給一個子產品,這些配置往往是application級别的資訊,需要一個手段将他們向下傳遞給子產品。在requireJS中,基于requirejs.config()的config配置項來實作。要擷取這些資訊的子產品可以加載特殊的依賴 ”moudle” ,并調用module.config().

首先我們可以還是試着做demo來了解下上面話的意思吧,我現在在項目requirejs下js/app檔案下建立一個d.js. 然後在app.js初始化檔案加入如下代碼:

requirejs.config({

config: {

'app/c': {

size: 'large'

},

'app/d': {

color: 'blue'

}

}

});

require(['app/c'],function(c){

console.log(c);

});

require(['app/d'],function(dss){

console.log(d);

});

在c.js裡面這樣寫代碼:

define(function (require, exports, module) {

//其值是'large'

var size = module.config().size;

return size;

});

在控制台下運作可以看到能列印出 large值出來,這說明我們可以通過config配置項來給app/c.js傳遞一個子產品資訊,比如如上面的一個對象{size:large},而在c.js裡面直接可以通過module.config()方法來擷取size的值。

下面我們可以使用一個依賴數組來做同樣的事情,如下d.js代碼:

define(['module'], function (module) {

//Will be the value 'blue'

var color = module.config().color;

return color;

});

在控制台看 也一樣可以列印出color值出來。

如果一個子產品不依賴其他子產品,那麼可以直接定義在define()函數之中。

假定現在有一個math.js檔案,它定義了一個math子產品。那麼,math.js就要這樣寫:

  // math.js

  define(function (){

    var add = function (x,y){

      return x+y;

    };

    return {

      add: add

    };

  });

加載方法如下:

  // main.js

  require(['math'], function (math){

    alert(math.add(1,1));

  });

jQuery的插件可以這樣定義:

  shim: {

    'jquery.scroll': {

      deps: ['jquery'],             deps數組,表明該子產品的依賴性。

      exports: 'jQuery.fn.scroll'

    }

  }

require.js還提供一系列插件,實作一些特定的功能。

domready插件,可以讓回調函數在頁面DOM結構加載完成後再運作。

  require(['domready!'], function (doc){

    // called once the DOM is ready

  });

text和image插件,則是允許require.js加載文本和圖檔檔案。

  define([

    'text!review.txt',

    'image!cat.jpg'

    ],

    function(review,cat){

      console.log(review);

      document.body.appendChild(cat);

    }

  );

類似的插件還有json和mdown,用于加載json檔案和markdown檔案。

  1. 插件形式的非AMD子產品,我們經常會用到jquery插件,而且這些插件基本都不符合AMD規範,比如jquery.form插件,這時候就需要将form插件"墊"到jquery中:
require.config({
    shim: {
        "underscore" : {
            exports : "_";
        },
        "jquery.form" : {
            deps : ["jquery"]
        }
    }
})      

也可以簡寫為:

require.config({
    shim: {
        "underscore" : {
            exports : "_";
        },
        "jquery.form" : ["jquery"]
    }
})      

這樣配置之後我們就可以使用加載插件後的jquery了

require.config(["jquery", "jquery.form"], function($){
    $(function(){
        $("#form").ajaxSubmit({...});
    })
})      

define 從名字就可以看出這個api是用來定義一個子產品

require 加載依賴子產品,并執行加載完後的回調函數(注意require中的依賴是一個數組,即使隻有一個依賴,你也必須使用數組來定義)

paths還有一個重要的功能,就是可以配置多個路徑,如果遠端cdn庫沒有加載成功,可以加載本地的庫

之前的例子中加載子產品都是本地js,但是大部分情況下網頁需要加載的JS可能來自本地伺服器、其他網站或CDN,這樣就不能通過這種方式來加載了

require.config({

    paths : {

        "jquery" : ["http://libs.baidu.com/jquery/2.0.3/jquery"]   

    }

})

callback函數中有$參數,這就是依賴的jquery子產品的輸出變量,如果你依賴多個子產品,可以依次寫入多個參數來使用:

require(["jquery","underscore"],function($, _){

    $(function(){

        _.each([1,2,3],alert);

    })

})

如果某個子產品不輸出變量值,則沒有,是以盡量将輸出的子產品寫在前面,防止位置錯亂引發誤解

require會預設的将data-main指定的js為根路徑,

requirejs入門到精通

AMD子產品規範

第一種寫法:

define(function() {

return {

mix: function(source, target) { }

};

});

第二種寫法 有依賴項如下:

define(['data', 'ui'], function(data, ui) {

// init here

});

第三種寫法 直接一個對象

define({

data: [],

ui: []

});

第四種寫法 有名子產品如下:

define('index', ['data','base'], function(data, base) {

// todo

});  

注:有名子產品要移動到其他目錄時,JS也要跟着改,是以代碼維護方面不好

第五種寫法 包裝子產品如下:

define(function(require, exports, module) {

var base = require('base');

exports.show = function() {

// todo with module base

}  

});

注:書寫格式和nodeJS比較像,可以使用require擷取子產品,使用exports或者module.exports導出API。

書寫requireJS遵循一個檔案一個子產品。

内部機制:

RequireJS加載的每個子產品作為script Tag,使用head.appendChild()方法。

在子產品的定義時,requireJS等到所有的依賴都加載完畢,會為函數的調用計算出正确的順序,然後在函數中通過正确的順序進行調用。

requireJS函數增加了第三個參數errbacks

requirejs入門到精通

在子產品載入失敗回調中可以使用undef函數移除子產品的注冊。

requirejs入門到精通

 轉載:http://www.tuicool.com/articles/jam2Anv

更多内容檢視API:http://www.requirejs.cn/