CMD(Common Module Definition)
表示通用模塊定義,該規範是國內發展出來的,由阿里的玉伯提出。就像AMD有個requireJS,CMD有個瀏覽器的實現SeaJS,SeaJS和requireJS同樣,都是javascript的模塊化解決方案。本文將詳細介紹CMD和seaJSjavascript
在Sea.js中,全部JavaScript模塊都遵循CMD(Common Module Definition)模塊定義規範。該規範明確了模塊的基本書寫格式和基本交互規則html
AMD規範簡單到只有一個API,即define函數java
define([module-name?], [array-of-dependencies?], [module-factory-or-object]);
module-name: 模塊標識,能夠省略node
array-of-dependencies: 所依賴的模塊,能夠省略jquery
module-factory-or-object: 模塊的實現,或者一個JavaScript對象git
CMD規範也與之相似,只不過第三個參數factory的實現方式不一樣。在CMD規範中,一個模塊就是一個文件。代碼的書寫格式以下github
define(id?, deps?, factory)
與AMD規範相似,define是一個全局函數,用來定義模塊。字符串 id
表示模塊標識,數組 deps
是模塊依賴。這兩個參數能夠省略,一般由構建工具自動生成數組
一般地,define()方法的第三個參數factory是一個函數,表示是模塊的構造方法。執行該構造方法,能夠獲得模塊向外提供的接口。factory
方法在執行時,默認會傳入三個參數:require
、exports
和 module
瀏覽器
[注意]factory()方法的參數若是不須要,能夠省略,但不能夠修改,如修改成'a'、'b'、'c',也不能夠改變其參數的順序。在函數內部,也不能對參數名從新賦值,如'var a = require; '網絡
define(function(require, exports, module) { // 模塊代碼 });
【require】
require
是 factory
函數的第一個參數。require
是一個方法,接受 模塊標識 做爲惟一參數,用來獲取其餘模塊提供的接口。通俗地說,經過require()方法來調用其餘模塊的屬性或方法
define(function(require, exports, module) { // 獲取模塊 a 的接口 var a = require('./a'); // 調用模塊 a 的方法 a.doSomething(); });
這個require()方法的實現和功能都特別相似於CommonJS中的require()方法。或許,有人會有疑惑,require()不是一個同步方法嗎?在CommonJS中是的,在seaJS中也能夠這麼說,但並不完整。更合理的說法應該是,模塊內的同步加載,實際表現爲對模塊a進行預下載
例以下面的代碼,即便不點擊頁面,a.js也會預先下載。點擊頁面後,控制檯依次輸出'a'和'a.test'
// main.js define(function(require, exports, module){ document.onclick = function(){ var a = require('js/a'); a.test(); } }); define(function(require, exports, module){ console.log('a'); exports.test = function(){ console.log('a.test'); } })
能不能執行時再下載呢?相似於懶加載。有的,使用require.async()方法。require.async
方法用來在模塊內部異步加載模塊,並在加載完成後執行指定回調
// main.js define(function(require, exports, module){ document.onclick = function(){ require.async('./a',function(a){ a.test(); }); } }); //a.js define(function(require, exports, module){ console.log('a'); exports.test = function(){ console.log('a.test'); } })
【exports】
exports
是一個對象,用來向外提供模塊接口。與CommonJS的exports功能相似
define(function(require, exports) { // 對外提供 foo 屬性 exports.foo = 'bar'; // 對外提供 doSomething 方法 exports.doSomething = function() {}; });
除了給 exports
對象增長成員,還可使用 return
直接向外提供接口,這種方式與requireJS的方式相似
define(function(require) { // 經過 return 直接提供接口 return { foo: 'bar', doSomething: function() {} }; });
若是 return
語句是模塊中的惟一代碼,還可簡化爲
define({ foo: 'bar', doSomething: function() {} });
【module】
module
是一個對象,上面存儲了與當前模塊相關聯的一些屬性和方法
// main.js define(['./a'],function(require, exports, module){ console.log(module); })
module.uri表示根據模塊系統的路徑解析規則獲得的模塊絕對路徑
module.id是模塊的惟一標識,通常狀況下沒有在define中手寫id參數時,module.id的值就是module.uri,二者徹底相同
module.dependencies是一個數組,表示當前模塊的依賴
module.exports是當前模塊對外提供的接口。傳給factory構造方法的exports參數是module.exports對象的一個引用。只經過exports參數來提供接口,有時沒法知足開發者的全部需求。 好比當模塊的接口是某個類的實例時,須要經過module.exports來實現
[注意]對module.exports
的賦值須要同步執行,不能放在回調函數裏。下面這樣是不行的
define(function(require, exports, module) { // 錯誤用法 setTimeout(function() { module.exports = { a: "hello" }; }, 0); });
requireJS經過data-main來設置入口,而seaJS則經過sea.use()來設置。sea.js 在下載完成後,會自動加載入口模塊
seajs.use(id, callback?)
[注意]callback
參數可選,省略時,表示無需回調
<script src="sea.js"></script> <script> seajs.use('js/main'); </script>
加載單個依賴,運行如下代碼後,控制檯輸出'test'
//index.html <script src="sea.js"></script> <script> seajs.config({ base: 'js' }); seajs.use("main",function(a){ a.test(); }); </script> // main.js define(['./a'],function(require, exports, module){ return { test : function(){ console.log('test'); } } })
加載多個依賴
//併發加載模塊 a 和模塊 b,並在都加載完成時,執行指定回調 seajs.use(['./a', './b'], function(a, b) { a.init(); b.init(); });
【DOMReady】
seajs.use
與DOM ready
事件沒有任何關係。若是某些操做要確保在DOM ready
後執行,須要使用jquery
等類庫來保證
seajs.use(['jquery', './main'], function($, main) { $(document).ready(function() { main.init(); }); });
【打包】
引入 sea.js
時,能夠把 sea.js
與其餘文件打包在一塊兒,可提早合併好,或利用 combo 服務動態合併。不管哪種方式,爲了讓 sea.js
內部能快速獲取到自身路徑,推薦手動加上 id
屬性
<script src="path/to/sea.js" id="seajsnode"></script>
加上 seajsnode
值,可讓 sea.js
直接獲取到自身路徑,而不須要經過其餘機制去自動獲取。這對性能和穩定性會有必定提高,推薦默認都加上
【路徑】
若是不配置路徑,在requireJS中,默認路徑是data-main的所處目錄,好比data-main='js/main',則所處路徑是'js'目錄下
而seaJS則不一樣,它的默認路徑是seaJS文件的所處目錄,好比seaJS文件所處路徑是'demo'目錄下,進行以下入口設置後
seajs.use('js/main');
說明main.js的目錄爲'demo/js/main.js'。若是main.js依賴於a.js,且a.js與main.js處於同一目錄下,則如下兩種寫法都正確
一、'demo' + 'js/a' = 'demo/js/a.js'
// main.js define(['js/a'],function(require, exports, module){ })
二、'./'表示當前目錄,即'demo/js',因此 './a' = 'demo/js/a.js'
// main.js define(['./a'],function(require, exports, module){ })
在requireJS中使用baseUrl來配置基礎路徑,而在seaJS中使用base。進行以下配置後,真實路徑爲 'demo' + 'js' + 'main' = 'demo/js/main.js'
【別名】
當模塊標識很長時,可使用 alias
來簡化
seajs.config({ alias: { 'jquery': 'jquery/jquery/1.10.1/jquery', 'app/biz': 'http://path/to/app/biz.js', } });
【目錄】
當目錄比較深,或須要跨目錄調用模塊時,可使用 paths
來簡化書寫
seajs.config({ paths: { 'gallery': 'https://a.alipayobjects.com/gallery', 'app': 'path/to/app', } });
AMD 是 RequireJS 在推廣過程當中對模塊定義的規範化產出,CMD 是 SeaJS 在推廣過程當中對模塊定義的規範化產出。這些規範的實現都能達成瀏覽器端模塊化開發的目的
AMD與CMD主要有如下兩點區別
一、所依賴模塊的執行時機
對於依賴的模塊,AMD是提早執行,CMD是延遲執行
AMD在加載模塊完成後就會執行該模塊,全部模塊都加載執行完後會進入require的回調函數,執行主邏輯,這樣的效果就是依賴模塊的執行順序和書寫順序不必定一致,看網絡速度,哪一個先下載下來,哪一個先執行,可是主邏輯必定在全部依賴加載完成後才執行。不過,新版本的RequireJS也能夠延遲執行
CMD加載完某個依賴模塊後並不執行,只是下載而已,在全部依賴模塊加載完成後進入主邏輯,遇到require語句的時候才執行對應的模塊,這樣模塊的執行順序和書寫順序是徹底一致的。若是使用require.async()方法,能夠實現模塊的懶加載,即不執行不下載
二、CMD推崇依賴就近,AMD推崇依賴前置
// CMD define(function(require, exports, module) { var a = require('./a') a.doSomething() // 此處略去 100 行 var b = require('./b') // 依賴能夠就近書寫 b.doSomething() // ... })
// AMD define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好 a.doSomething() // 此處略去 100 行 b.doSomething() ... })
固然,AMD也支持CMD的寫法,同時還支持將require做爲依賴項傳遞
CommonJS、requireJS、seaJS這三種模塊化方案,並無高低之分。隨着各個方案的不斷升級,語言方面相互借鑑,使用差別逐漸變小。以上三種庫級別的模塊化方案,須要引入額外的庫,且所遵循的規範並非標準組織制定的,權威性不足
隨着ES6在語言層面上開始支持模塊化,ES6的模塊化寫法纔是將來的模塊化標準