最近寫一個小遊戲的時候用的是RequireJs構建項目,順便補了一下RequireJs,下面講解一些基礎和進階的用法。
AMD
AMDAsync Module Definition代表的意思為異步模塊定義,是Javascript模塊化的瀏覽器解決方案,它採用異步的方式加載模塊,模塊的加載不影響它後面語句的運行。所有依賴這個模塊的語句,都定義在回調函數中,等到加載完成之後,這個回調函數才會運行。
AMD規範定義了一個函數define,通過define方法定義模塊:
define(id?, dependencies?, factory);
並且採用require()語句加載模塊:
require([module], callback);
引入的模塊和回調函數不是同步的,所以瀏覽器不會因為引入的模塊加載不成功而假死。
RequireJS
RequireJS是一個基於AMD規範實現的JavaScript文件和模塊加載器。它針對瀏覽器內使用進行了優化,並且對其他JS環境(Rhino和Node)做了兼容。使用RequireJS這樣的模塊化腳本加載器可以提高代碼的速度和質量。
- 異步加載: 使用 RequireJS,會在相關的 js 加載後執行回調函數,這個過程是異步的,所以它不會阻塞頁面。
- 按需加載: 通過 RequireJS,你可以在需要加載 js 邏輯的時候再加載對應的 js 模塊,不需要的模塊就不加載,這樣避免了在初始化網頁的時候發生大量的請求和數據傳輸。
基本使用
根據官方文檔和項目實例,接下來説一下ReuqireJS的基本使用:
Reuqire Download
下載最新版的RequireJS。
Project Structure
下面是官方示例的RequireJS項目結構,對內容做了小小的改動,www作為項目的根目錄,lib中存放項目依賴即需要的一些JS庫,app.js為主入口文件,app中存放自己寫的模塊文件。
Project Code
1. index.html
index.html中定義了一個script標籤來引入require.js,其中data-main屬性是一個自定義屬性,這個屬性指定在加載完 reuqire.js 後,就將屬性指定路徑下的JS文件並運行,這個文件即入口文件,這裏的app.js的js後綴被省略掉了。
<!DOCTYPE html>
<html>
<head>
<script data-main="app" src="lib/require.js"></script>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
如果 <script/>~~~~ 標籤引入 require.js 時沒有指定 data-main 屬性,則以引入該 js 的 html 文件所在的路徑為根路徑,如果有指定 data-main 屬性,也就是有指定入口文件,則以入口文件所在的路徑為根路徑。
2. app.js
Main.js加載主模塊並且配置項目依賴,要改變 RequireJS的默認配置,可以使用require.config函數傳入一個可選參數對象。下面是一些可以使用的配置:
// For any third party dependencies, like jQuery, place them in the lib folder.
// Configure loading modules from the lib directory,
// except for 'app' ones, which are in a sibling
// directory.
requirejs.config({
// 模塊加載的根路徑。
baseUrl: ".",
// 用於一些常用庫文件或者文件夾路徑映射,js後綴可以省略
paths: {
app: "app/",
fmt: "lib/fmt",
},
});
// Start loading the main app file. Put all of
// your application logic in there.
requirejs(["app/main"]);
如果在 require.config() 中有配置 baseUrl,則以 baseUrl 的路徑為根路徑,這條規則會覆蓋上面data-main的效果。
3. app/
Main.js中我們通過require函數加載了一個message模塊,該模塊用於打印一些定義好的字符串。
define(function (require) {
var msg = require("./message");
msg.helloWorld();
});
Main.js中使用的模塊定義在message.js中,他引入了一個輸出依賴fmt。
define(["fmt"], function (fmt) {
return {
helloWorld: function () {
fmt.println("hello word");
},
};
});
這兩種依賴的加載方式又和不同稍後介紹。
4. lib/
Lib/fmt.js中我定義一個 js 模塊模擬go的fmt包,通過return對外暴露出接口。注意,暴露的對象就是引入的對象。
define(function () {
var print = function (msg) {
console.log(msg);
};
var println = function (msg) {
console.log(msg + "\n");
};
return {
moduleName: "fmt",
print: print,
println: println,
};
});
require.config 函數配置
要改變RequireJS的默認配置,可以使用require.config函數傳入一個可選參數對象,其實這個對象可以配置五個屬性:
require.config({
baseUrl: '.',
paths: {
app: "app/",
fmt: "lib/fmt",
},
shim: {
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
},
'underscore': {
exports: '_'
}
},
config: {
'app/main': {
ENV: 'development'
}
},
map: {
'script/newmodule': {
'foo': 'foo1.2'
},
'script/oldmodule': {
'foo': 'foo1.0'
}
},
});
1. baseUrl
baseUrl作為加載模塊的根路徑。在配置這個屬性後,之後所有的路徑定義都是基於這個根路徑的(包括配置和依賴引入中)。
2. path
用於一些常用庫文件或者文件夾路徑映射,定義之後可以直接在依賴引入中使用。
3. shim
雖然目前已經有一部分流行的函數庫符合 AMD 規範,但還有很多庫並不符合。shim就是為了加載這些非AMD規範的js,並解決其載入順序的,比如上面的backbone。
4. config
config將配置信息傳給一個模塊。這些配置往往是application級別的信息,需要一個手段將它們向下傳遞給模塊。
config: {
'app/main': {
ENV: 'development'
}
}
可以通過加載特殊的依賴module來獲取這些信息。
define(['module'], function (module) {
var ENV = module.config().ENV; // development
var msg = require("./message");
msg.helloWorld();
});
5. map
對於給定的模塊前綴,使用一個不同的模塊 ID 來加載該模塊。該手段對於某些大型項目很重要。比如上面配置以後,不同的模塊會使用不同版本的foo。
當some/newmodule調用了require('foo'),它將獲取到foo1.2.js文件,當oldmodule調用 require('foo'),時它將獲取到 foo1.0.js 文件。
map還支持*,意思是“對於所有的模塊加載,使用本 map 配置”。如果還有更細化的 map 配置,會優先於*配置。
requirejs.config({
map: {
'*': {
'foo': 'foo1.2'
},
'some/oldmodule': {
'foo': 'foo1.0'
}
}
});
模塊定義
1. 對象
如果一個模塊僅含鍵值對,沒有任何依賴,可以直接在define中定義。
define({
foo: "foo",
bar: function(){}
});
2. 需要預處理的對象
define(function () {
console.log("Do something...");
return {
foo: "foo",
bar: function(){}
}
});
3. 只有一個函數
沒有任何依賴的函數直接這麼定義:
define(function () {
return function (){};
});
調用時直接打()調用:
require(['lib/foo'],function (foo) {
foo();
});
4. 需要其他的依賴:
define(['jquery'],function($){
return function (){};
});
循環加載
假設我們有如下 a、b 兩個互相依賴的模塊,我們如果調用 b 模塊的 b 方法。
// app/a.js
define(['app/b'],function(b){
return function() { b() }
});
// app/b.js
define(['app/a'],function(a){
return function() { a() }
});
會發現 b 調用 a 正常,但是 a 中調用 b 方法會報 undefined 錯誤。
require(['app/b'],function (b) {
b(); // b is not defined.
});
解決:
循環依賴比較罕見,對於循環依賴,只要依賴環中任何一條邊是運行時依賴,這個環理論上就是活的。而如果全部邊都是裝載時依賴,這個環就是死的。
對模塊 a 進行如下修改,即不再依賴前置加載。而是通過引入 require 依賴,然後再通過 require() 方法去載入模塊 b,並在回調中去執行。
// app/a.js
define(['require'],function(require){
var b = require('b')
return function() {
b()
}
});