天天看點

requireJs詳解(二)

requirejs中define和require的定位以及使用差別?

define定義require加載使用,define定義的子產品可以被其它調用 require不行.

define和require在依賴處理和回調執行上都是一樣的,不一樣的地方是define的回調函數需要有return語句傳回子產品對象,這樣define定義的子產品才能被其他子產品引用;require的回調函數不需要return語句

define([require,path/myMod1,path/moMod2], function(require){  
    var mod1 = require('path/myMod1'), mod2 = require('path/myMod2');
})

其實邏輯上類似于

define([require], function(require){  
    var mod1 = require('path/myMod1'), mod2 = require('path/myMod2');
})

你隻是把參數丢了而已,是以這樣也行

define([require,path/myMod1,path/moMod2], function(require){  
    var mod1 = arguments[1], mod2 = arguments[2];
})

是以,可以這麼用

define([require,path/myMod1,path/moMod2], function(require,mod1,mod2){  
    // 這裡 mod1 和 mod2 都準備好了,還可以用require繼續加載别的
})

或者,改成你看到的那些例子

define([path/myMod1,path/moMod2], function(mod1,mod2){  
})



是以

define(['jquery.validate'], function(validate){
    // 這裡可以用validate,但是用不了jquery了?
})

這樣本質上并沒有問題,隻不過你引用了這個插件,必然也是想用jquery的而不隻是validate,

是以

define(['jquery', 'jquery.validate'], function(jquery, validate){
    // 這樣才有jquery
})

像你一開始那樣使用也行

define(['require', 'jquery.validate'], function(require, validate){
    var jquery = require('jquery');
})
           

其實這裡又要扯到AMD和CMD的差別,依賴前置和依賴就近。

define([path/myMod1,path/moMod2], function(mod1,mod2){  
})
           

是依賴前置。

define(function(require, exports, module) {
    var $ = require('jquery');
})
           

這是依賴後置。

後者可以用來解決循環引用問題。。

第二個問題: 關于requirejs.config 中shim

題主了解有點偏差,requirejs.config 中shim作用是 為那些沒有使用define()來聲明依賴關系、設定子產品的"浏覽器全局變量注入"型腳本做依賴和導出配置。

簡單來說就是對requirejs要引用的第三方非AMD規範的插件、類庫的特殊處理。通常有兩種方法:

A: AMD化.通過define封裝一下

B: config shim.

另外使用seajs的時候也要注意非CMD插件的CMD化。

補充:require和define差別。使用方法一樣

define(['./jquery'], function($) { console.log($); });
require(['./jquery'], function($) { console.log($); });
           

都能加載到jquery. 但是define與require不同的就是它多出來一個接口的傳回。是以當把define寫成require後你會發現别的module引用不到本次定義的module.

使用例子

requirejs.config({
    baseUrl: 'libs',
    paths: {
        "core1": 'cores/core1',
        "core2": 'cores/core2',
        "util1": 'utils/util1',
        "util2": 'utils/util2',
        "service1": 'services/service1',
        "service2": 'services/service2',
    }
});
 
require(["core1","core2","util1","util2","service1","service2"], function() {
     
    var core1 = require("core1");
    var core2 = require("core2");
    var util1 = require("util1");
    var util2 = require("util2");
    var service1 = require("service1");
    var service2 = require("service2");
 
});
           
  1. 聲明不同js檔案之間的依賴
  2. 可以按需、并行、延時載入js庫
  3. 可以讓我們的代碼以子產品化的方式組織

初看起來并不複雜。

在html中引入requirejs

在HTML中,添加這樣的 

<script>

 标簽:

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

屬性 

data-main

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

在config.js中配置requirejs

config.js

 中通常用來做兩件事:

  1. 配置requirejs 比如項目中用到哪些子產品,檔案路徑是什麼
  2. 載入程式主子產品
requirejs.config({
  baseUrl: '/public/js',
  paths: {
    app: 'app'
  }
});

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

在 

paths

 中,我們聲明了一個名為 

app

 的子產品,以及它對應的js檔案位址。在最理想的情況下, 

app.js

 的内容,應該使用requirejs的方式來定義子產品:

define([], function() {
  return {
    hello: function() {
      alert("hello, app~");
    }
  }
});
                

這裡的 

define

 是requirejs提供的函數。requirejs一共提供了兩個全局變量:

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

另外還可以把 

require

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

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

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

前面的代碼是理想的情況,即依賴的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方式的代碼中的某一個全局變量暴露出去,當作該子產品以引用。

暴露多個變量: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);
});
                

它會提示我:

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

 .

是以當我們使用另一個名字:

去引用這個庫的時候,它會發現,在 

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

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

$

 可能又重新被污染了。

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

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