CMD和seaJS
CMD(Common Module Definition)
表示通用模塊定義,該規範是國內發展出來的,由阿裏的玉伯提出。就像AMD有個requireJS,CMD有個瀏覽器的實現SeaJS,SeaJS和requireJS一樣,都是javascript的模塊化解決方案。本文將詳細介紹CMD和seaJS
CMD
在Sea.js中,所有JavaScript模塊都遵循CMD(Common Module Definition)模塊定義規範。該規範明確了模塊的基本書寫格式和基本交互規則
AMD規範簡單到只有一個API,即define函數
define([module-name?], [array-of-dependencies?], [module-factory-or-object]);
module-name: 模塊標識,可以省略
array-of-dependencies: 所依賴的模塊,可以省略
module-factory-or-object: 模塊的實現,或者一個JavaScript對象
CMD規範也與之類似,只不過第三個參數factory的實現方式不同。在CMD規範中,一個模塊就是一個文件。代碼的書寫格式如下
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.jsdefine(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.jsdefine(function(require, exports, module){ document.onclick = function(){ require.async(‘./a‘,function(a){ a.test(); }); } });//a.jsdefine(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.jsdefine([‘./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.jsdefine([‘./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處於同一目錄下,則以下兩種寫法都正確
1、‘demo‘ + ‘js/a‘ = ‘demo/js/a.js‘
// main.jsdefine([‘js/a‘],function(require, exports, module){ })
2、‘./‘表示當前目錄,即‘demo/js‘,所以 ‘./a‘ = ‘demo/js/a.js‘
// main.jsdefine([‘./a‘],function(require, exports, module){ })
在requireJS中使用baseUrl來配置基礎路徑,而在seaJS中使用base。進行如下配置後,真實路徑為 ‘demo‘ + ‘js‘ + ‘main‘ = ‘demo/js/main.js‘
<script src="sea.js"></script> <script> seajs.config({ base: ‘js‘ }); seajs.use("main");</script>
【別名】
當模塊標識很長時,可以使用 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區別
AMD 是 RequireJS 在推廣過程中對模塊定義的規範化產出,CMD 是 SeaJS 在推廣過程中對模塊定義的規範化產出。這些規範的實現都能達成瀏覽器端模塊化開發的目的
AMD與CMD主要有以下兩點區別
1、所依賴模塊的執行時機
對於依賴的模塊,AMD是提前執行,CMD是延遲執行
AMD在加載模塊完成後就會執行該模塊,所有模塊都加載執行完後會進入require的回調函數,執行主邏輯,這樣的效果就是依賴模塊的執行順序和書寫順序不一定一致,看網絡速度,哪個先下載下來,哪個先執行,但是主邏輯一定在所有依賴加載完成後才執行。不過,新版本的RequireJS也可以延遲執行
CMD加載完某個依賴模塊後並不執行,只是下載而已,在所有依賴模塊加載完成後進入主邏輯,遇到require語句的時候才執行對應的模塊,這樣模塊的執行順序和書寫順序是完全一致的。如果使用require.async()方法,可以實現模塊的懶加載,即不執行不下載
2、CMD推崇依賴就近,AMD推崇依賴前置
// CMDdefine(function(require, exports, module) { var a = require(‘./a‘) a.doSomething() // 此處略去 100 行 var b = require(‘./b‘) // 依賴可以就近書寫 b.doSomething() // ... })
// AMDdefine([‘./a‘, ‘./b‘], function(a, b) { // 依賴必須一開始就寫好 a.doSomething() // 此處略去 100 行 b.doSomething() ... })
當然,AMD也支持CMD的寫法,同時還支持將require作為依賴項傳遞
最後
CommonJS、requireJS、seaJS這三種模塊化方案,並沒有高低之分。隨著各個方案的不斷升級,語言方面相互借鑒,使用差異逐漸變小。以上三種庫級別的模塊化方案,需要引入額外的庫,且所遵循的規範並不是標準組織制定的,權威性不足
隨著ES6在語言層面上開始支持模塊化,ES6的模塊化寫法才是未來的模塊化標準
CMD和seaJS