1. 程式人生 > >CMD和seaJS

CMD和seaJS

js

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

方法在執行時,默認會傳入三個參數:requireexportsmodule

  [註意]factory()方法的參數如果不需要,可以省略,但不可以修改,如修改為‘a‘、‘b‘、‘c‘,也不可以改變其參數的順序。在函數內部,也不能對參數名重新賦值,如‘var a = require; ‘

define(function(require, exports, module) {  // 模塊代碼});

【require】

  requirefactory 函數的第一個參數。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.useDOM 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