為什么要使用模塊化?
-
最主要的目的:
- 解決命名沖突
- 依賴管理
-
其他價值
- 提高代碼可讀性
- 代碼解耦,提高復用性
CMD、AMD、CommonJS 規范分別指什么?有哪些應用
這三個規范都是為javascript模塊化加載而生的,都是在用到或者預計要用到某些模塊時候加載該模塊,使得大量的系統巨大的龐雜的代碼得以很好的組織和管理。模塊化使得我們在使用和管理代碼的時候不那么混亂,而且也方便了多人的合作。
CMD規范
- CMD(Common Module Definition)通用模板定義,它是在一個瀏覽器端模塊化的開發規范
- 使用CMD規范進行開發需要使用SeaJS
- Sea.js推薦一個模塊為一個文件
代碼的書寫格式:
define(id?,dependencies?,factory);
- id:(可不寫,默認文件名),用來定義模塊的標識,通用文件名作為模塊ID
- dependencies:(可不寫)一個當前模塊依賴的模塊名稱數組(因為CMD推崇依賴就近,因此一般不在此處指定)
- factory:
- function(require,exports,module)
- require(id):require是一個方法,接受模塊標識作為唯一參數,用來獲取其他模塊提供的接口
- exports是一個對象,用來向外提供模塊接口
- module是一個對象,上面儲存了與當前模塊相關聯的一些屬性和方法
define Function
define是一個全局函數,用來定義模塊
define接受factory參數,factory可以是一個函數,也可以是一個對象或字符串
factory為對象、字符串時,表示模塊的接口就是該對象、字符串。例如定義一個JSON數據模塊:
define({ “foo”:“bar” });
factory為函數時,表示模塊的構造方法。執行該構造方法,可以得到模塊向外提供的接口。factory方法在執行時,默認傳入三個參數:require、exports、module:
define(function(require,exports,module){
})
define define(id?, deps?, factory)
define 也可以接受兩個以上參數。字符串 id 表示模塊標識,數組 deps 是模塊依賴。比如:
define('hello', ['jquery'], function(require, exports, module) {
});
id和 deps參數可以省略。省略時,可以通過構建工具自動生成。
注意:帶 id和 deps參數的 define用法不屬于 CMD 規范,而屬于 Modules/Transport 規范。
define.cmd Object
一個空對象,可用來判定當前頁面是否有 CMD 模塊加載器:
if (typeof define === "function" && define.cmd) {
// 有 Sea.js 等 CMD 模塊加載器存在
}
require Function
require是factory函數的第一個參數
require是一個方法,接受 模塊標識 作為唯一參數,用來獲取其他模塊提供的接口。
define(function(require, exports) {
// 獲取模塊 a 的接口
var a = require('./a');
// 調用模塊 a 的方法
a.doSomething();
});
require.async require.async(id, callback?)
require.async 方法用來在模塊內部異步加載模塊,并在加載完成后執行指定回調。callback 參數可選。
define(function(require, exports, module) {
// 異步加載一個模塊,在加載完成時,執行回調
require.async('./b', function(b) {
b.doSomething();
});
// 異步加載多個模塊,在加載完成時,執行回調
require.async(['./c', './d'], function(c, d) {
c.doSomething();
d.doSomething();
});
});
注意:require 是同步往下執行,require.async 則是異步回調執行。require.async 一般用來加載可延遲異步加載的模塊。
require.resolve require.resolve(id)
使用模塊系統內部的路徑解析機制來解析并返回模塊路徑。該函數不會加載模塊,只返回解析后的絕對路徑。
define(function(require, exports) {
console.log(require.resolve('./b'));
// ==> http://example.com/path/to/b.js
});
這可以用來獲取模塊路徑,一般用在插件環境或需動態拼接模塊路徑的場景下。
exports Object
exports 是一個對象,用來向外提供模塊接口。
define(function(require, exports) {
// 對外提供 foo 屬性
exports.foo = 'bar';
// 對外提供 doSomething 方法
exports.doSomething = function() {};
});
除了給 exports 對象增加成員,還可以使用 return 直接向外提供接口。
define(function(require) {
// 通過 return 直接提供接口
return {
foo: 'bar',
doSomething: function() {}
};
});
如果 return 語句是模塊中的唯一代碼,還可簡化為:
define({
foo: 'bar',
doSomething: function() {}
});
上面這種格式特別適合定義 JSONP 模塊。
特別注意:下面這種寫法是錯誤的!
define(function(require, exports) {
// 錯誤用法!!!
exports = {
foo: 'bar',
doSomething: function() {}
};
});
正確的寫法是用 return 或者給 module.exports 賦值:
define(function(require, exports, module) {
// 正確寫法
module.exports = {
foo: 'bar',
doSomething: function() {}
};
});
提示:exports 僅僅是 module.exports 的一個引用。在 factory 內部給 exports 重新賦值時,并不會改變 module.exports 的值。因此給 exports 賦值是無效的,不能用來更改模塊接口。
module Object
module 是一個對象,上面存儲了與當前模塊相關聯的一些屬性和方法。
module.id String
模塊的唯一標識
define('id', [], function(require, exports, module) {
// 模塊代碼
});
上面代碼中,define 的第一個參數就是模塊標識。
module.uri String
根據模塊系統的路徑解析規則得到的模塊絕對路徑。
define(function(require, exports, module) {
console.log(module.uri);
// ==> http://example.com/path/to/this/file.js
});
一般情況下(沒有在 define 中手寫 id 參數時),module.id 的值就是 module.uri,兩者完全相同。
module.dependencies Array
dependencies 是一個數組,表示當前模塊的依賴。
module.exports Object
當前模塊對外提供的接口。
傳給 factory 構造方法的 exports 參數是 module.exports 對象的一個引用。只通過 exports 參數來提供接口,有時無法滿足開發者的所有需求。 比如當模塊的接口是某個類的實例時,需要通過 module.exports 來實現:
define(function(require, exports, module) {
// exports 是 module.exports 的一個引用
console.log(module.exports === exports); // true
// 重新給 module.exports 賦值
module.exports = new SomeClass();
// exports 不再等于 module.exports
console.log(module.exports === exports); // false
});
注意:對 module.exports 的賦值需要同步執行,不能放在回調函數里。下面這樣是不行的:
// x.js
define(function(require, exports, module) {
// 錯誤用法
setTimeout(function() {
module.exports = { a: "hello" };
}, 0);
});
在 y.js 里有調用到上面的 x.js:
// y.js
define(function(require, exports, module) {
var x = require('./x');
// 無法立刻得到模塊 x 的屬性 a
console.log(x.a); // undefined
});
小結
經常使用的 API 只有 define, require, require.async, exports, module.exports 這五個。其他 API 有個印象就好。
與 RequireJS 的 AMD 規范相比,CMD 規范盡量保持簡單,并與 CommonJS 和 Node.js 的 Modules 規范保持了很大的兼容性。通過 CMD 規范書寫的模塊,可以很容易在 Node.js 中運行。
AMD規范
- AMD (Asynchronous Module Definition, 異步模塊定義) 指定一種機制,在該機制下模塊和依賴可以異步加載。這對瀏覽器端的異步加載尤其適用。
- AMD 是 RequireJS 在推廣過程中對模塊定義的規范化產出。
- 使用AMD規范進行開發需要使用RequireJS
- requireJS主要解決兩個問題:
- js文件之間的依賴關系:被依賴的文件需要早于依賴它的文件加載到瀏覽器
- js加載的時候瀏覽器會停止頁面渲染,加載文件越多,頁面失去響應時間越長
define(id?, dependencies?, factory);
id: 定義中模塊的名字,可選;如果沒有提供該參數,模塊的名字應該默認為模塊加載器請求的指定腳本的名字。
依賴dependencies:是一個當前模塊依賴的,已被模塊定義的模塊標識的數組字面量。 依賴參數是可選的,如果忽略此參數,它應該默認為["require", "exports", "module"]。然而,如果工廠方法的長度屬性小于3,加載器會選擇以函數的長度屬性指定的參數個數調用工廠方法。
工廠方法factory,模塊初始化要執行的函數或對象。如果為函數,它應該只被執行一次。如果是對象,此對象應該為模塊的輸出值。
AMD模式
define和require這兩個定義模塊、調用模塊的方法,合稱為AMD模式。它的模塊定義的方法非常清晰,不會污染全局環境,能夠清楚地顯示依賴關系。
AMD模式可以用于瀏覽器環境,并且允許非同步加載模塊,也可以根據需要動態加載模塊。
define方法:定義模塊
define方法用于定義模塊,RequireJS要求每個模塊放在一個單獨的文件里。
按照是否依賴其他模塊,可以分成兩種情況討論。第一種情況是定義獨立模塊,即所定義的模塊不依賴其他模塊;第二種情況是定義非獨立模塊,即所定義的模塊依賴于其他模塊。
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');
});
CommonJS規范
CommonJS是服務器端模塊的規范,Node.js采用了這個規范。Node.JS首先采用了js模塊化的概念。
1.在一個模塊中,存在一個自由的變量“require”,它是一個函數。
- 這個“require”函數接收一個模塊標識符
- “require”返回外部模塊所輸出的API
- 如果出現依賴閉環(dependency cycle),那么外部模塊在被它的傳遞依賴(transitive dependencies)所require的時候可能并沒有執行完成;在這種情況下,”require”返回的對象必須至少包含此外部模塊在調用require函數(會進入當前模塊執行環境)之前就已經準備完畢的輸出。
- 如果請求的模塊不能返回,那么”require”必須拋出一個錯誤。
2.在一個模塊中,會存在一個名為“exports”的自由變量,它是一個對象,模塊可以在執行的時候把自身的API加入到其中。
3.模塊必須使用“exports”對象來做為輸出的唯一表示。
根據這個規范,每個文件就是一個模塊,有自己的作用域。在一個文件里面定義的變量、函數、類,都是私有的,對其他文件不可見。
// example.js
var x = 5;
var addX = function (value) {
return value + x;
};
上面代碼中,變量X和函數addX,是當前文件example.js私有的,其他文件不可見。如果想在多個文件分享變量,必須定義為global對象的屬性。
global.warning = true;
上面代碼的warning變量,可以被所有文件讀取。當然,這樣寫法是不推薦的。
CommonJS規范規定,每個模塊內部,module變量代表當前模塊。這個變量是一個對象,它的exports屬性(即module.exports)是對外的接口。加載某個模塊,其實是加載該模塊的module.exports屬性。
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
上面代碼通過module.exports輸出變量x和函數addX。
require方法用于加載模塊。
var example = require('./example.js');
console.log(example.x); // 5
console.log(example.addX(1)); // 6
CommonJS模塊的特點:
- 所有代碼都運行在模塊作用域,不會污染全局作用域。
- 模塊可以多次加載,但是只會在第一次加載時運行一次,然后運行結果就被緩存了,以后再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。
- 模塊加載的順序,按照其在代碼中出現的順序。
RequireJS模塊化加載實例
【個人總結,如有錯漏,歡迎指出】
:>