requirejs(一)

RequireJs已經流行好久了,咱們在項目中也打算使用它。它提供瞭如下功能:javascript

  1. 聲明不一樣js文件之間的依賴
  2. 能夠按需、並行、延時載入js庫
  3. 可讓咱們的代碼以模塊化的方式組織

初看起來並不複雜。php

在html中引入requirejs

在HTML中,添加這樣的 <script> 標籤:css

<script src="/path/to/require.js" data-main="/path/to/app/config.js"></script> 

一般使用requirejs的話,咱們只須要導入requirejs便可,不須要顯式導入其它的js庫,由於這個工做會交給requirejs來作。html

屬性 data-main 是告訴requirejs:你下載完之後,立刻去載入真正的入口文件。它通常用來對requirejs進行配置,而且載入真正的程序模塊。java

在config.js中配置requirejs

config.js 中一般用來作兩件事:jquery

  1. 配置requirejs 好比項目中用到哪些模塊,文件路徑是什麼
  2. 載入程序主模塊
requirejs.config({
  baseUrl: '/public/js', paths: { app: 'app' } }); requirejs(['app'], function(app) { app.hello(); }); 

在 paths 中,咱們聲明瞭一個名爲 app 的模塊,以及它對應的js文件地址。在最理想的狀況下, app.js 的內容,應該使用requirejs的方式來定義模塊:ruby

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

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

  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 .

因此當咱們使用另外一個名字:

myjquery: 'lib/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 的依賴,使得它最早被執行。

相關文章
相關標籤/搜索