seajs的CMD模式的優點以及使用

seajs的CMD模式的優點以及使用

以前有一篇博客很是詳細的介紹了sea.js的加載流程,以及源代碼實現,連接地址:http://www.cnblogs.com/chaojidan/p/4123980.htmljavascript

這篇博客我主要講下sea.js的介紹和使用。css

首先,先介紹下sea.js的CMD規範,以及跟其餘規範的區別。html

CommonJS 原來叫 ServerJS,推出 Modules/1.0 規範後,在 Node.js 等環境下取得了很不錯的實踐。前端

09年下半年這幫充滿幹勁的小夥子們想把 ServerJS 的成功經驗進一步推廣到瀏覽器端,因而將社區更名叫 CommonJS,同時激烈爭論 Modules 的下一版規範。分歧和衝突由此誕生,逐步造成了三大流派:java

  1. Modules/1.x 流派。這個觀點以爲 1.x 規範已經夠用,只要移植到瀏覽器端就好。主流表明是服務端的開發人員。
  2. Modules/Async 流派。這個觀點以爲瀏覽器有自身的特徵,不該該直接用 Modules/1.x 規範。這個觀點下的典型表明是 AMD 規範及其實現 RequireJS
  3. Modules/2.0 流派。這個觀點以爲瀏覽器有自身的特徵,不該該直接用 Modules/1.x 規範,但應該儘量與 Modules/1.x 規範保持一致。這個觀點下的典型表明是 BravoJS 和 FlyScript 的做者。BravoJS 做者對 CommonJS 的社區的貢獻很大,這份 Modules/2.0-draft 規範花了不少心思。FlyScript 的做者提出了 Modules/Wrappings 規範,這規範是 CMD 規範的前身。惋惜的是 BravoJS 太學院派,FlyScript 後來作了自我閹割,將整個網站(flyscript.org)下線了。

第二流派:AMD 與 RequireJSjquery

AMD 風格下,經過參數傳入依賴模塊,破壞了就近聲明 (須要時,才聲明)原則。好比:git

define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { 
    // 等於在最前面聲明並初始化了要用到的全部模塊
   if (false) {
       // 即使沒用到某個模塊 b,但 b 仍是提早執行了
       b.foo()
   } 
})

第三流派:Modules/2.0  CMD模塊github

CMD 裏,默認推薦的是json

define(function(require, exports, module) {     //a,b模塊只下載好了,而且只執行了模塊中的define方法,而define方法中的function要等到require時,纔會執行
  var a = require('a');     //延遲執行了a,b模塊
  var b = require('b');        
  // do sth
})

 

區別:數組

1. 對於依賴的模塊,AMD 是提早執行,CMD 是延遲執行。不過 RequireJS 從 2.0 開始,也改爲能夠延遲執行(根據寫法不一樣,處理方式不一樣)。CMD 推崇 as lazy as possible.

2. 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 做爲依賴項傳遞,但 RequireJS 的做者默認是最喜歡上面的寫法,也是官方文檔裏默認的模塊定義寫法。

3. AMD 的 API 默認是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一

好比 AMD 裏,require 分全局 require 和局部 require,都叫 require。CMD 裏,沒有全局 require,而是根據模塊系統的完備性,提供 seajs.use 來實現模塊系統的加載啓動。CMD 裏,每一個 API 都簡單純粹

CMD 可使得構建時的複雜度下降。

目前 Sea.js 擁有 plugin-combo 插件,模塊的合併能夠放在線上動態作。有些狀況下(比較容易出現),動態 combo 的地址會很長:

https://a.alipaybojects.com/??path/to/a.js,path/to/b.js..................path/to/long-url.js

當 url 地址很長時,超過 2083(好像是這個值),在 IE 以及一些服務器配置下,過長的 url 會出問題。這時經典的解決辦法是將 url 拆分紅多段:

https://a.alipaybojects.com/??path/to/a.js,path/to/b.js..................path/to/u.js

https://a.alipaybojects.com/??path/to/f.js,path/to/g.js..................path/to/long-url.js

拆分後,在 CMD 規範下,上面兩個 url 能夠併發同時請求,誰先返回都沒問題。但在 AMD 下,上面的需求,就掛了,很難實現。

你會說 RequireJS 鼓勵的是項目上線前,經過構建工具先構建好,不須要線上 combo,也就不會遇到上面的問題。

Sea.js 放得更寬泛,提早合併好,仍是線上時才動態 combo,對 CMD 模塊來講均可行。不少時候,combo 真心省事,也更天然。前端開發並不是到處要像 Java 同樣引入嚴格的構建過程。

CMD 的懶執行策略,也更有利於頁面性能。

RequireJS 2.0 後,很多理念也在悄悄地發生着變化,如今好像也支持懶執行了。

 

而後,介紹下sea.js的方法和使用。

type="text/javascript" src="js/seajs/2.0.0/sea-debug.js?t=123" data-config="sea-js-config.js?t=123"

上面的data-config是指sea.js的配置文件的路徑。還有一個屬性是data-main,它是項目的起始模塊,若是定義了會先執行此模塊。data-main是可選項。

首先咱們來看看sea-js-config.js

seajs.config({
     // 配置插件
   plugins: ['shim'],
     // 配置別名
   alias: {
       // 配置 jquery 的 shim 配置,這樣咱們就能夠經過 require('jquery') 來獲取 jQuery
     'jquery': {
          src: 'libs/jquery/1.9.1/jquery.js',  //注意,這裏是從sea.js所在目錄的上兩節目錄開始查找文件
            exports: 'jQuery'
       }
   }
});

plugins選項配置插件,這裏使用了shim插件。因爲jquery不是一個標準的CMD模塊,因此直接加載jquery是錯誤的。這裏使用了shim插件,會自動把jquery轉換成一個標準的CMD模塊。不用人工改動jquery源碼。alias是配置別名,方便加載的。

看個例子:


項目主模塊app.js
define(function(require, exports, module) {
    //加載jquery, 並把它$設爲全局變量
   window.$ = window.jQuery = $ = require('jquery');
      //定義一個全局的模塊加載函數.module爲模塊名,options爲參數
    exports.script_load = function(module, options) {
        //使用require.async異步加載模塊。模塊須要提供一個固定的對外調用接口,這裏定義爲run。
        require.async('modules/' + module, function(module) {
       if (typeof(module.run) === 'function') {
                  module.run(options);
       }
         });
    }
    window.script_load = exports.script_load
});
上面咱們加載了jquery, 而且定義了一個模塊加載函數。如今咱們在html頁面加入以下代碼:
<script type="text/javascript">
     seajs.use('modules/app', function(app) {
           app.script_load('index');
  });
</script>
use方法執行時,會先加載app模塊,加載並執行完後,就進入function中,這時就會調用app.script_load方法,此方法就會去加載index模塊,加載完成後,執行index中的代碼,index中會返回run方法。index執行完畢後,會調用require.async的回調方法:

if (typeof(module.run) === 'function') {
                  module.run(options);
}

所以index模塊中返回了run方法,所以就執行index中的run方法。

index.js
define(function(require, exports, module) {
   exports.run = function() {
         $('#alert').click(function() {
        alert('彈出了一個框!');
         });
  }
});

 

SeaJS中使用「define」函數定義一個模塊,define能夠接收三個參數,
define能夠接收的參數分別是模塊ID,依賴模塊數組及工廠函數。
我閱讀源代碼後發現define對於不一樣參數個數的解析規則以下:
若是隻有一個參數,則賦值給factory。
若是有兩個參數,第二個賦值給factory;第一個若是是array則賦值給deps,不然賦值給id。
若是有三個參數,則分別賦值給id,deps和factory。
id是一個模塊的標識字符串,define只有一個參數時,id會被默認賦值爲此js文件的絕對路徑。
如example.com下的a.js文件中使用define定義模塊,則這個模塊的ID會賦值爲 http://example.com/a.js ,沒有特別的必要建議不要傳入id。deps通常也不須要傳入,須要用到的模塊用require加載便可。
工廠函數function是模塊的主體和重點。在只傳遞一個參數給define時(推薦寫法),這個參數就是工廠函數,此時工廠函數的三個參數分別是:
• require——模塊加載函數,用於記載依賴模塊。
• exports——接口點,將數據或方法定義在其上則將其暴露給外部調用。
• module——模塊的元數據。
module是一個對象,存儲了模塊的元信息,具體以下:
• module.id——模塊的ID。
• module.dependencies——一個數組,存儲了此模塊依賴的全部模塊的ID列表。
• module.exports——與exports指向同一個對象。

 

三種編寫模塊的模式:

第一種定義模塊的模式是基於exports的模式:

define(function(require, exports, module) {
  var a = require('a'); //引入a模塊
     var b = require('b'); //引入b模塊

     var data1 = 1; //私有數據

  var func1 = function() { //私有方法
    return a.run(data1);
  }

     exports.data2 = 2; //公共數據

     exports.func2 = function() { //公共方法
    return 'hello';
  }
});

上面是一種比較「正宗」的模塊定義模式。除了將公共數據和方法附加在exports上,也能夠直接返回一個對象表示模塊,以下面的代碼與上面的代碼功能相同:(第二種)

define(function(require, exports, module) {
  var a = require('a'); //引入a模塊
     var b = require('b'); //引入b模塊

     var data1 = 1; //私有數據

  var func1 = function() { //私有方法
    return a.run(data1);
  }

   return {
    data2: 2,
           func2: function() {
               return 'hello';
    }
  };

});

若是模塊定義沒有其它代碼,只返回一個對象,還能夠有以下簡化寫法。第三種方法對於定義純JSON數據的模塊很是合適。

define({

    data: 1,

    func: function() {

        return 'hello';

   }

});

 

絕對地址——給出js文件的絕對路徑。如

require("http://example/js/a");

就表明載入 http://example/js/a.js 。

基址地址——若是載入字符串標識既不是絕對路徑也不是以」./」開頭的相對地址,則相對SeaJS全局配置中的「base」來尋址。

注意上面在載入模塊時都不用傳遞後綴名「.js」,SeaJS會自動添加「.js」。可是下面三種狀況下不會添加:

載入css時,如

require("./module1-style.css");

路徑中含有」?」時,如

require(<a href="http://example/js/a.json?cb=func">http://example/js/a.json?cb=func</a>);

路徑以」#」結尾時,如

require("http://example/js/a.json#");

根據應用場景的不一樣,SeaJS提供了三個載入模塊的API,分別是seajs.use,require和require.async。

seajs.use主要用於載入入口模塊。入口模塊至關於C程序的main函數,同時也是整個模塊依賴樹的根。seajs.use用法以下:

//單一模式

seajs.use('./a');

//回調模式

seajs.use('./a', function(a) {

    a.run();

});

//多模塊模式

seajs.use(['./a', './b'], function(a, b) {

    a.run();

  b.run();

 });

通常seajs.use只用在頁面載入入口模塊,SeaJS會順着入口模塊解析全部依賴模塊並將它們加載。若是入口模塊只有一個,也能夠經過給引入sea.js的script標籤加入」data-main」屬性來省略seajs.use,例如,

<!DOCTYPE HTML>

<html lang="zh-CN">

<head>

  <meta charset="UTF-8">

 <title>TinyApp</title>

</head>

<body>

   <p class="content"></p>

   <script src="./sea.js" data-main="./init"></script>

</body>

</html>

 

傳給require的路徑標識必須是字符串字面量,不能是表達式,以下面使用require的方法是錯誤的:
 require('module' + '1');
 require('Module'.toLowerCase());
這都會形成SeaJS沒法進行正確的正則匹配如下載相應的js文件。
上文說過SeaJS會在html頁面打開時經過靜態分析,一次性下載全部須要的js文件,若是想要某個js文件在用到時才下載,可使用require.async。

require.async('/path/to/module/file', function(m) {
  //code of callback...
});
這樣只有在用到這個模塊時,對應的js文件纔會被下載,也就實現了JavaScript代碼的按需加載。

SeaJS提供了一個seajs.config方法能夠設置全局配置,接收一個表示全局配置的配置對象。
seajs.config({
  base: 'path/to/jslib/',
  alias: {
         'app': 'path/to/app/'
  },
  charset: 'utf-8',
  timeout: 20000,
  debug: false
});
其中base表示基址尋址時的基址路徑。例如base設置爲 http://example.com/js/3-party/ ,則
var $ = require('jquery');
會載入 http://example.com/js/3-party/jquery.js 。
alias能夠對較長的經常使用路徑設置縮寫。
charset表示下載js時script標籤的charset屬性。
timeout表示下載文件的最大時長,以毫秒爲單位。
debug表示是否工做在調試模式下。
要將現有JS庫如jQuery與SeaJS一塊兒使用,只需根據SeaJS的的模塊定義規則對現有庫進行一個封裝。例如,下面是對jQuery的封裝方法:
define(function() {

   //{{{jQuery原有代碼開始

   //}}}jQuery原有代碼結束

   return $.noConflict();
});

 

特別注意:下面這種寫法是錯誤的!
define(function(require, exports) {

  // 錯誤用法!!!
  exports = {
    foo: 'bar',
    doSomething: function() {}
  };

});
正確的寫法是用 return 或者給 module.exports 賦值:
define(function(require, exports, module) {

  // 正確寫法
  module.exports = {
    foo: 'bar',
    doSomething: function() {}
  };

});
提示:exports 僅僅是 module.exports 的一個引用。在 factory 內部給 exports 從新賦值時,並不會改變 module.exports 的值。所以給 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 的賦值須要同步執行,不能放在回調函數裏。下面這樣是不行的:

define(function(require, exports, module) {

  // 錯誤用法
  setTimeout(function() {
    module.exports = { a: "hello" };
  }, 0);

});
seajs.config({
  alias: {
  'jquery': 'jquery/1.7.2/jquery-debug.js'
}
});

seajs.use(['./a','jquery'],function(a,$){
  var num = a.a;
  $('#J_A').text(num);
})

use方法將會從咱們的config配置信息中查看 ,是否有預先須要被加載的模塊。若是有,就先加載,沒有就加載a和jquery模塊。

相關文章
相關標籤/搜索