快速理解RequireJs
RequireJs已經流行很久了,我們在專案中也打算使用它。它提供了以下功能:
- 宣告不同js檔案之間的依賴
- 可以按需、並行、延時載入js庫
- 可以讓我們的程式碼以模組化的方式組織
初看起來並不複雜。
在html中引入requirejs
在HTML中,新增這樣的 <script>
標籤:
<script src="/path/to/require.js" data-main="/path/to/app/config.js"></script>
通常使用requirejs的話,我們只需要匯入requirejs即可,不需要顯式匯入其它的js庫,因為這個工作會交給requirejs來做。
屬性 data-main
是告訴requirejs:你下載完以後,馬上去載入真正的入口檔案。它一般用來對requirejs進行配置,並且載入真正的程式模組。
在config.js中配置requirejs
config.js
中通常用來做兩件事:
- 配置requirejs 比如專案中用到哪些模組,檔案路徑是什麼
- 載入程式主模組
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一共提供了兩個全域性變數:
- requirejs/require: 用來配置requirejs及載入入口模組。如果其中一個命名被其它庫使用了,我們可以用另一個
- 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
的依賴,使得它最先被執行。
相關文章
- requirejs 配製UIJS
- RequireJS入門指南UIJS
- ASM之快速理解ASM
- Backbone React Requirejs 應用實戰(一)——RequireJS管理React依賴ReactUIJS
- RequireJS shim 用法說明UIJS
- RequireJS原始碼分析(上)UIJS原始碼
- 已有外掛支援requirejsUIJS
- requirejs、backbone.js配置UIJS
- AngularJS與RequireJS整合方案AngularJSUI
- 快速理解併發、並行並行
- 快速理解HBase和BigTable
- 快速排序的簡單理解排序
- 【模組化程式設計】理解requireJS-實現一個簡單的模組載入器程式設計UIJS
- 實現一個requirejs原型demoUIJS原型
- RequireJS中的require如何返回模組UIJS
- Part 2: Google's APIs 和 RequireJSGoAPIUIJS
- 快速理解 volatile 關鍵字
- 新手入門,如何快速理解JavaScriptJavaScript
- 05-快速理解SparkSQL的DataSetSparkSQL
- 快速理解MySQL null的10大坑MySqlNull
- requirejs vue vue.router簡單框架UIJSVue框架
- vue、rollup、sass、requirejs組成的vueManagerVueUIJS
- RequireJS 模組化程式設計詳解UIJS程式設計
- RequireJs學習筆記之Define a ModuleUIJS筆記
- Http網路協議包 (快速理解)HTTP協議
- 【Spring】快速理解迴圈依賴Spring
- Beam Search快速理解及程式碼解析
- 快速理解網路通訊協議協議
- seajs和requirejs技術指導文件JSUI
- requireJS(版本是2.1.15)學習教程(一)UIJS
- 充分理解Kotlin,快速上手寫業務Kotlin
- MySQL(二):快速理解MySQL資料庫索引MySql資料庫索引
- 快速理解容器技術的實現原理
- 快速理解JavaScript中call和apply原理JavaScriptAPP
- 理解快速傅立葉變換(FFT)演算法FFT演算法
- 基於requirejs的vue2專案 (三)UIJSVue
- 基於requirejs的vue2專案(二)UIJSVue
- requireJS對檔案合併與壓縮(二)UIJS