標籤:out 改變 自動產生 target 動態 logs 方案 輸出 hello
前面的話
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