[TOC]
## 概述
RequireJS是一個工具庫,主要用于客戶端的模塊管理。它可以讓客戶端的代碼分成一個個模塊,實現異步或動態加載,從而提高代碼的性能和可維護性。它的模塊管理遵守[AMD規范](https://github.com/amdjs/amdjs-api/wiki/AMD)(Asynchronous Module Definition)。
RequireJS的基本思想是,通過define方法,將代碼定義為模塊;通過require方法,實現代碼的模塊加載。
首先,將require.js嵌入網頁,然后就能在網頁中進行模塊化編程了。
~~~
<script data-main="scripts/main" src="scripts/require.js"></script>
~~~
上面代碼的data-main屬性不可省略,用于指定主代碼所在的腳本文件,在上例中為scripts子目錄下的main.js文件。用戶自定義的代碼就放在這個main.js文件中。
### define方法:定義模塊
define方法用于定義模塊,RequireJS要求每個模塊放在一個單獨的文件里。
按照是否依賴其他模塊,可以分成兩種情況討論。第一種情況是定義獨立模塊,即所定義的模塊不依賴其他模塊;第二種情況是定義非獨立模塊,即所定義的模塊依賴于其他模塊。
(1)獨立模塊
如果被定義的模塊是一個獨立模塊,不需要依賴任何其他模塊,可以直接用define方法生成。
~~~
define({
method1: function() {},
method2: function() {},
});
~~~
上面代碼生成了一個擁有method1、method2兩個方法的模塊。
另一種等價的寫法是,把對象寫成一個函數,該函數的返回值就是輸出的模塊。
~~~
define(function () {
return {
method1: function() {},
method2: function() {},
};
});
~~~
后一種寫法的自由度更高一點,可以在函數體內寫一些模塊初始化代碼。
值得指出的是,define定義的模塊可以返回任何值,不限于對象。
(2)非獨立模塊
如果被定義的模塊需要依賴其他模塊,則define方法必須采用下面的格式。
~~~
define(['module1', 'module2'], function(m1, m2) {
...
});
~~~
define方法的第一個參數是一個數組,它的成員是當前模塊所依賴的模塊。比如,['module1', 'module2']表示我們定義的這個新模塊依賴于module1模塊和module2模塊,只有先加載這兩個模塊,新模塊才能正常運行。一般情況下,module1模塊和module2模塊指的是,當前目錄下的module1.js文件和module2.js文件,等同于寫成['./module1', './module2']。
define方法的第二個參數是一個函數,當前面數組的所有成員加載成功后,它將被調用。它的參數與數組的成員一一對應,比如function(m1, m2)就表示,這個函數的第一個參數m1對應module1模塊,第二個參數m2對應module2模塊。這個函數必須返回一個對象,供其他模塊調用。
~~~
define(['module1', 'module2'], function(m1, m2) {
return {
method: function() {
m1.methodA();
m2.methodB();
}
};
});
~~~
上面代碼表示新模塊返回一個對象,該對象的method方法就是外部調用的接口,menthod方法內部調用了m1模塊的methodA方法和m2模塊的methodB方法。
需要注意的是,回調函數必須返回一個對象,這個對象就是你定義的模塊。
如果依賴的模塊很多,參數與模塊一一對應的寫法非常麻煩。
~~~
define(
[ 'dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6', 'dep7', 'dep8'],
function(dep1, dep2, dep3, dep4, dep5, dep6, dep7, dep8){
...
}
);
~~~
為了避免像上面代碼那樣繁瑣的寫法,RequireJS提供一種更簡單的寫法。
~~~
define(
function (require) {
var dep1 = require('dep1'),
dep2 = require('dep2'),
dep3 = require('dep3'),
dep4 = require('dep4'),
dep5 = require('dep5'),
dep6 = require('dep6'),
dep7 = require('dep7'),
dep8 = require('dep8');
...
}
});
~~~
下面是一個define實際運用的例子。
~~~
define(['math', 'graph'],
function ( math, graph ) {
return {
plot: function(x, y){
return graph.drawPie(math.randomGrid(x,y));
}
}
};
);
~~~
上面代碼定義的模塊依賴math和graph兩個庫,然后返回一個具有plot接口的對象。
另一個實際的例子是,通過判斷瀏覽器是否為IE,而選擇加載zepto或jQuery。
~~~
define(('__proto__' in {} ? ['zepto'] : ['jquery']), function($) {
return $;
});
~~~
上面代碼定義了一個中間模塊,該模塊先判斷瀏覽器是否支持proto屬性(除了IE,其他瀏覽器都支持),如果返回true,就加載zepto庫,否則加載jQuery庫。
### require方法:調用模塊
require方法用于調用模塊。它的參數與define方法類似。
~~~
require(['foo', 'bar'], function ( foo, bar ) {
foo.doSomething();
});
~~~
上面方法表示加載foo和bar兩個模塊,當這兩個模塊都加載成功后,執行一個回調函數。該回調函數就用來完成具體的任務。
require方法的第一個參數,是一個表示依賴關系的數組。這個數組可以寫得很靈活,請看下面的例子。
~~~
require( [ window.JSON ? undefined : 'util/json2' ], function ( JSON ) {
JSON = JSON || window.JSON;
console.log( JSON.parse( '{ "JSON" : "HERE" }' ) );
});
~~~
上面代碼加載JSON模塊時,首先判斷瀏覽器是否原生支持JSON對象。如果是的,則將undefined傳入回調函數,否則加載util目錄下的json2模塊。
require方法也可以用在define方法內部。
~~~
define(function (require) {
var otherModule = require('otherModule');
});
~~~
下面的例子顯示了如何動態加載模塊。
~~~
define(function ( require ) {
var isReady = false, foobar;
require(['foo', 'bar'], function (foo, bar) {
isReady = true;
foobar = foo() + bar();
});
return {
isReady: isReady,
foobar: foobar
};
});
~~~
上面代碼所定義的模塊,內部加載了foo和bar兩個模塊,在沒有加載完成前,isReady屬性值為false,加載完成后就變成了true。因此,可以根據isReady屬性的值,決定下一步的動作。
下面的例子是模塊的輸出結果是一個promise對象。
~~~
define(['lib/Deferred'], function( Deferred ){
var defer = new Deferred();
require(['lib/templates/?index.html','lib/data/?stats'],
function( template, data ){
defer.resolve({ template: template, data:data });
}
);
return defer.promise();
});
~~~
上面代碼的define方法返回一個promise對象,可以在該對象的then方法,指定下一步的動作。
如果服務器端采用JSONP模式,則可以直接在require中調用,方法是指定JSONP的callback參數為define。
~~~
require( [
"http://someapi.com/foo?callback=define"
], function (data) {
console.log(data);
});
~~~
require方法允許添加第三個參數,即錯誤處理的回調函數。
~~~
require(
[ "backbone" ],
function ( Backbone ) {
return Backbone.View.extend({ /* ... */ });
},
function (err) {
// ...
}
);
~~~
require方法的第三個參數,即處理錯誤的回調函數,接受一個error對象作為參數。
require對象還允許指定一個全局性的Error事件的監聽函數。所有沒有被上面的方法捕獲的錯誤,都會被觸發這個監聽函數。
~~~
requirejs.onError = function (err) {
// ...
};
~~~
### AMD模式小結
define和require這兩個定義模塊、調用模塊的方法,合稱為AMD模式。它的模塊定義的方法非常清晰,不會污染全局環境,能夠清楚地顯示依賴關系。
AMD模式可以用于瀏覽器環境,并且允許非同步加載模塊,也可以根據需要動態加載模塊。
## 配置require.js:config方法
require方法本身也是一個對象,它帶有一個config方法,用來配置require.js運行參數。config方法接受一個對象作為參數。
~~~
require.config({
paths: {
jquery: [
'//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',
'lib/jquery'
]
}
});
~~~
config方法的參數對象有以下主要成員:
(1)paths
paths參數指定各個模塊的位置。這個位置可以是同一個服務器上的相對位置,也可以是外部網址。可以為每個模塊定義多個位置,如果第一個位置加載失敗,則加載第二個位置,上面的示例就表示如果CDN加載失敗,則加載服務器上的備用腳本。需要注意的是,指定本地文件路徑時,可以省略文件最后的js后綴名。
~~~
require(["jquery"], function($) {
// ...
});
~~~
上面代碼加載jquery模塊,因為jquery的路徑已經在paths參數中定義了,所以就會到事先設定的位置下載。
(2)baseUrl
baseUrl參數指定本地模塊位置的基準目錄,即本地模塊的路徑是相對于哪個目錄的。該屬性通常由require.js加載時的data-main屬性指定。
(3)shim
有些庫不是AMD兼容的,這時就需要指定shim屬性的值。shim可以理解成“墊片”,用來幫助require.js加載非AMD規范的庫。
~~~
require.config({
paths: {
"backbone": "vendor/backbone",
"underscore": "vendor/underscore"
},
shim: {
"backbone": {
deps: [ "underscore" ],
exports: "Backbone"
},
"underscore": {
exports: "_"
}
}
});
~~~
上面代碼中的backbone和underscore就是非AMD規范的庫。shim指定它們的依賴關系(backbone依賴于underscore),以及輸出符號(backbone為“Backbone”,underscore為“_”)。
## 插件
RequireJS允許使用插件,加載各種格式的數據。完整的插件清單可以查看[官方網站](https://github.com/jrburke/requirejs/wiki/Plugins)。
下面是插入文本數據所使用的text插件的例子。
~~~
define([
'backbone',
'text!templates.html'
], function( Backbone, template ){
// ...
});
~~~
上面代碼加載的第一個模塊是backbone,第二個模塊則是一個文本,用'text!'表示。該文本作為字符串,存放在回調函數的template變量中。
## 優化器r.js
RequireJS提供一個基于node.js的命令行工具r.js,用來壓縮多個js文件。它的主要作用是將多個模塊文件壓縮合并成一個腳本文件,以減少網頁的HTTP請求數。
第一步是安裝r.js(假設已經安裝了node.js)。
~~~
npm install -g requirejs
~~~
然后,使用的時候,直接在命令行鍵入以下格式的命令。
~~~
node r.js -o <arguments>
~~~
表示命令運行時,所需要的一系列參數,比如像下面這樣:
~~~
node r.js -o baseUrl=. name=main out=main-built.js
~~~
除了直接在命令行提供參數設置,也可以將參數寫入一個文件,假定文件名為build.js。
~~~
({
baseUrl: ".",
name: "main",
out: "main-built.js"
})
~~~
然后,在命令行下用r.js運行這個參數文件,就OK了,不需要其他步驟了。
~~~
node r.js -o build.js
~~~
下面是一個參數文件的范例,假定位置就在根目錄下,文件名為build.js。
~~~
({
appDir: './',
baseUrl: './js',
dir: './dist',
modules: [
{
name: 'main'
}
],
fileExclusionRegExp: /^(r|build)\.js$/,
optimizeCss: 'standard',
removeCombined: true,
paths: {
jquery: 'lib/jquery',
underscore: 'lib/underscore',
backbone: 'lib/backbone/backbone',
backboneLocalstorage: 'lib/backbone/backbone.localStorage',
text: 'lib/require/text'
},
shim: {
underscore: {
exports: '_'
},
backbone: {
deps: [
'underscore',
'jquery'
],
exports: 'Backbone'
},
backboneLocalstorage: {
deps: ['backbone'],
exports: 'Store'
}
}
})
~~~
上面代碼將多個模塊壓縮合并成一個main.js。
參數文件的主要成員解釋如下:
* appDir:項目目錄,相對于參數文件的位置。
* baseUrl:js文件的位置。
* dir:輸出目錄。
* modules:一個包含對象的數組,每個對象就是一個要被優化的模塊。
* fileExclusionRegExp:凡是匹配這個正則表達式的文件名,都不會被拷貝到輸出目錄。
* optimizeCss: 自動壓縮CSS文件,可取的值包括“none”, “standard”, “standard.keepLines”, “standard.keepComments”, “standard.keepComments.keepLines”。
* removeCombined:如果為true,合并后的原文件將不保留在輸出目錄中。
* paths:各個模塊的相對路徑,可以省略js后綴名。
* shim:配置依賴性關系。如果某一個模塊不是AMD模式定義的,就可以用shim屬性指定模塊的依賴性關系和輸出值。
* generateSourceMaps:是否要生成source map文件。
更詳細的解釋可以參考[官方文檔](https://github.com/jrburke/r.js/blob/master/build/example.build.js)。
運行優化命令后,可以前往dist目錄查看優化后的文件。
下面是另一個build.js的例子。
~~~
({
mainConfigFile : "js/main.js",
baseUrl: "js",
removeCombined: true,
findNestedDependencies: true,
dir: "dist",
modules: [
{
name: "main",
exclude: [
"infrastructure"
]
},
{
name: "infrastructure"
}
]
})
~~~
上面代碼將模塊文件壓縮合并成兩個文件,第一個是main.js(指定排除infrastructure.js),第二個則是infrastructure.js。
## 參考鏈接
* NaorYe,?[Optimize (Concatenate and Minify) RequireJS Projects](http://www.webdeveasy.com/optimize-requirejs-projects/)
* Jonathan Creamer,?[Deep dive into Require.js](http://tech.pro/tutorial/1300/deep-dive-into-requirejs)
* Addy Osmani,?[Writing Modular JavaScript With AMD, CommonJS & ES Harmony](http://addyosmani.com/writing-modular-js/)
* Jim Cowart,?[Five Helpful Tips When Using RequireJS](http://tech.pro/blog/1561/five-helpful-tips-when-using-requirejs)
* Jim Cowart,?[Using r.js to Optimize Your RequireJS Project](http://tech.pro/blog/1639/using-rjs-to-optimize-your-requirejs-project)
- 第一章 導論
- 1.1 前言
- 1.2 為什么學習JavaScript?
- 1.3 JavaScript的歷史
- 第二章 基本語法
- 2.1 語法概述
- 2.2 數值
- 2.3 字符串
- 2.4 對象
- 2.5 數組
- 2.6 函數
- 2.7 運算符
- 2.8 數據類型轉換
- 2.9 錯誤處理機制
- 2.10 JavaScript 編程風格
- 第三章 標準庫
- 3.1 Object對象
- 3.2 Array 對象
- 3.3 包裝對象和Boolean對象
- 3.4 Number對象
- 3.5 String對象
- 3.6 Math對象
- 3.7 Date對象
- 3.8 RegExp對象
- 3.9 JSON對象
- 3.10 ArrayBuffer:類型化數組
- 第四章 面向對象編程
- 4.1 概述
- 4.2 封裝
- 4.3 繼承
- 4.4 模塊化編程
- 第五章 DOM
- 5.1 Node節點
- 5.2 document節點
- 5.3 Element對象
- 5.4 Text節點和DocumentFragment節點
- 5.5 Event對象
- 5.6 CSS操作
- 5.7 Mutation Observer
- 第六章 瀏覽器對象
- 6.1 瀏覽器的JavaScript引擎
- 6.2 定時器
- 6.3 window對象
- 6.4 history對象
- 6.5 Ajax
- 6.6 同域限制和window.postMessage方法
- 6.7 Web Storage:瀏覽器端數據儲存機制
- 6.8 IndexedDB:瀏覽器端數據庫
- 6.9 Web Notifications API
- 6.10 Performance API
- 6.11 移動設備API
- 第七章 HTML網頁的API
- 7.1 HTML網頁元素
- 7.2 Canvas API
- 7.3 SVG 圖像
- 7.4 表單
- 7.5 文件和二進制數據的操作
- 7.6 Web Worker
- 7.7 SSE:服務器發送事件
- 7.8 Page Visibility API
- 7.9 Fullscreen API:全屏操作
- 7.10 Web Speech
- 7.11 requestAnimationFrame
- 7.12 WebSocket
- 7.13 WebRTC
- 7.14 Web Components
- 第八章 開發工具
- 8.1 console對象
- 8.2 PhantomJS
- 8.3 Bower:客戶端庫管理工具
- 8.4 Grunt:任務自動管理工具
- 8.5 Gulp:任務自動管理工具
- 8.6 Browserify:瀏覽器加載Node.js模塊
- 8.7 RequireJS和AMD規范
- 8.8 Source Map
- 8.9 JavaScript 程序測試
- 第九章 JavaScript高級語法
- 9.1 Promise對象
- 9.2 有限狀態機
- 9.3 MVC框架與Backbone.js
- 9.4 嚴格模式
- 9.5 ECMAScript 6 介紹
- 附錄
- 10.1 JavaScript API列表
- 草稿一:函數庫
- 11.1 Underscore.js
- 11.2 Modernizr
- 11.3 Datejs
- 11.4 D3.js
- 11.5 設計模式
- 11.6 排序算法
- 草稿二:jQuery
- 12.1 jQuery概述
- 12.2 jQuery工具方法
- 12.3 jQuery插件開發
- 12.4 jQuery.Deferred對象
- 12.5 如何做到 jQuery-free?
- 草稿三:Node.js
- 13.1 Node.js 概述
- 13.2 CommonJS規范
- 13.3 package.json文件
- 13.4 npm模塊管理器
- 13.5 fs 模塊
- 13.6 Path模塊
- 13.7 process對象
- 13.8 Buffer對象
- 13.9 Events模塊
- 13.10 stream接口
- 13.11 Child Process模塊
- 13.12 Http模塊
- 13.13 assert 模塊
- 13.14 Cluster模塊
- 13.15 os模塊
- 13.16 Net模塊和DNS模塊
- 13.17 Express框架
- 13.18 Koa 框架