CMD(Common Module Definition,通用模塊定義)是一種模塊定義規范,規范中明確了模塊的基本書寫格式和基本交互規則。SeaJS就是遵循的這個規范。
define函數
在CMD規范中,一個模塊就是一個文件,模塊的區分通過define關鍵字來定義,最基本的格式是:
define(factory); // 這里的define是一個全局函數,factory可以是函數、對象或字符串
define接受factory參數,factory可以是一個函數,也可以是一個對象或字符串。
當factory為對象、字符串時,表示模塊的接口就是該對象、字符串。
比如可以如下定義一個JSON數據模塊:
define({ "foo": "bar" });
也可以通過字符串定義模板模塊:
define('I am a template. My name is {{name}}.');
當factory為函數時,表示是模塊的構造方法。執行該構造方法,可以得到模塊向外提供的接口。
當factory方法在執行時,默認會傳入三個參數:require、exports和module:
define(function(require, exports, module) { // 模塊代碼 });
define也可以接受兩個以上的參數,進階的格式是:
define(id, deps, factory); // id是模塊標識字符串,deps是模塊依賴數組
參數中,字符串id表示模塊標注,數組deps表示模塊依賴。
define('helloworld', ['jquery'], function(require, exports, module) { // 模塊代碼 });
id和deps參數是可以省略的,省略時可以通過構建工具自動生成。注意的是帶id和deps參數的define用法不屬於CMD規范,而屬於Modules/Transport規范。
define函數帶有成員屬性cmd,是一個空對象,可用來判斷當前頁面是否有CMD模塊加載器。
if (typeof define === "function" && define.cmd) { // 有 Sea.js 等 CMD 模塊加載器存在時的邏輯代碼 }
require函數(factory函數的參數之一)
require是factory函數的第一個參數,它有下列的用法:
require(id),通過接受模塊標識作為唯一參數,用來獲取其他模塊提供的接口。
define(function(require, exports) { // 獲取模塊 a 的接口 var a = require('./helloworlda'); // 調用模塊 helloworld 的方法 a.doSomething(); });
require.async(id, callback),用來在模塊內部加載模塊,並在加載完成后執行指定回調。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.resolve(id),使用模塊系統內部的路徑解析機制來解析並返回模塊路徑,該函數並不會加載模塊,只返回解析后的絕對路徑。
define(function(require, exports) { console.log(require.resolve('./b'));// http://path/b.js });
這樣就可以獲取模塊的路徑,一般用在插件環境或需要動態拼接模塊路徑的場景下。
exports對象(factory函數的參數之一)
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語句是模塊中的唯一代碼,還可以簡化為(factory為對象或字符串):
define({ foo: 'bar', doSomething: function() {} });
上面這種格式特別適合定義一個JSONP模塊。
要注意的是,exports僅僅是module.exports的一個引用,在factory內部給exports重新賦值時,並不會改變module.exports的值,因此給exports賦值是無效的,不能用來更改模塊接口。比如以下的寫法就是錯誤的:
define(function(require, exports) { // 錯誤用法!!! exports = { // 應該改為module.exports = {} foo: 'bar', doSomething: function() {} }; });
module對象(factory函數的參數之一)
module是一個對象,存儲與當前模塊相關的一些屬性和方法。
module.id,模塊的唯一標識。
define('yanggb', [], function(require, exports, module) { // 模塊代碼 console.log(module.id); // yanggb });
define函數的第一個參數就是模塊唯一標識,用module取得時候就能得到yanggb。
module.uri,模塊的絕對路徑,根據模塊系統的路徑解析規則得到。
define(function(require, exports, module) { // 模塊代碼 console.log(module.uri); // http://path/file.js });
一般情況下(沒有在define函數中自定義id參數),module.id的值就是module.uri的值。
module.dependencies,模塊的依賴,是一個數組。
module.exports,模塊對外提供的接口。
傳給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,在 y.js 里有調用到上面的 x.js: define(function(require, exports, module) { var x = require('./x'); // 無法立刻得到模塊 x 的屬性 a (有延遲) console.log(x.a); // undefined });
總結
這里學習的順序就是:define函數 -> factory函數(define函數的參數) -> require函數、exports對象和modules對象(factory函數的參數)。其實用法也比較簡單,對開發很有幫助,有助於開發思路的整理和后期代碼的維護。優點也就是模塊化編程的優點。
"我唱的不夠動人你別皺眉。"