小談AMD與CMD

小談AMD與CMD

命名衝突和文件依賴,是前端開發過程中的兩個經典問題。人們嘗試通過模塊化開發方法和思維來解決這些問題。

Sea.js與CMD模塊化規範

簡介

Sea.js 是一個適用於 Web 瀏覽器端的模塊加載器。遵循CMD模塊化標準。

定義模塊

define(function(require, exports, module){

})

引入模塊

var foo = require('./foo.js') // .js可以被省略

注意require的參數,即路徑必須是字符串直接量,不得是任何形式的表達式。

異步加載模塊

require對模塊進行同步加載,如果想要異步加載模塊,可以使用

require.async('./foo.js', function(foo){
// do something after the module is loaded
foo.doSomething();
})
// 或
require.async(['./foo.js','./bar.js'], function(foo, bar){

})

路徑

路徑有相對、頂級與普通之分。相對路徑相對於當前路徑的uri進行解析,特徵是以./或../開頭。頂級路徑前沒有/或.,相對於模塊系統的基礎路徑(即 Sea.js 的 base 路徑)來解析。也可以設置普通路徑,即絕對路徑或根路徑(/)。此外sea.use()中的路徑始終是普通路徑。此外,Sea.js會爲每個沒有後綴名的文件自動加上js後綴,如果不想這麼做,要麼爲文件加上後綴名,要麼在文件名後面加#。

導出模塊

exports.foo = foo;
//或
module.exports = bar;
// 或
return {
    foo:foo,
    bar:bar
}

注意:導出語句必須同步執行,不能放在比如setTimeout等函數的回調中。

配置

seajs.config({
  // 別名配置
  alias: {
    'es5-safe': 'gallery/es5-safe/0.9.3/es5-safe',
    'json': 'gallery/json/1.0.2/json',
    'jquery': 'jquery/jquery/1.10.1/jquery'
  },
  // 路徑配置,paths 配置可以結合 alias 配置一起使用,讓模塊引用非常方便。
  paths: {
    'gallery': 'https://a.alipayobjects.com/gallery'
  },
  // 變量配置,有時路徑只有在運行時才能知道,可以通過{locale}獲取配置的值
  vars: {
    'locale': 'zh-cn'
  },
  // 映射配置
  map: [
    ['http://example.com/js/app/', 'http://localhost/js/app/']
  ],
  // 預加載項
  preload: [
    Function.prototype.bind ? '' : 'es5-safe',
    this.JSON ? '' : 'json'
  ],

  // 調試模式
  debug: true,

  // Sea.js 的基礎路徑
  base: 'http://example.com/path/to/base/',

  // 文件編碼
  charset: 'utf-8'
});

此外,seajs.config 可以多次運行,每次運行時,會對配置項進行合併操作。

啓動

seajs.use(['jquery', './main'], function($, main) {
  $(document).ready(function() {
    main.init();
  });
});

其它

獲得文件(模塊)的絕對路徑

require.resolve('./foo.js'); // =>http://www.hukaihe.cn/static/foo.js
// 或
module.uri('./foo.js')

獲得當前模塊所依賴的模塊

module.dependencies 

Seajs可以方便的跑在Nodejs端

// 讓 Node 環境可以加載執行 CMD 模塊
require('seajs');
var a = require('./a');

設計原則

  1. 關注度分離。比如書寫模塊 a.js 時,如果需要引用 b.js,則只需要知道 b.js 相對 a.js 的相對路徑即可,無需關注其他。
  2. 儘量與瀏覽器的解析規則一致。比如根路徑(/xx/zz)、絕對路徑、以及傳給 use 方法的非頂級標識,都是相對所在頁面的 URL 進行解析。

AMD 與requirejs

RequireJS的目標是鼓勵代碼的模塊化,它使用了不同於傳統

baseUrl

RequireJS以一個相對於baseUrl的地址來加載所有的代碼。 我們可以在require.config中對baseUrl進行設置,但如果未設置之,則默認與data-main所指定的文件爲同一目錄,如果未指定data-main屬性,那麼以引入requirejs的html地址爲baseUrl。

此外RequireJS默認假定所有的依賴資源都是js腳本,因此無需在module ID上再加”.js”後綴。RequireJS腳本的加載是支持跨域的。

RequireJS使用head.appendChild()將每一個依賴加載爲一個script標籤。RequireJS等待所有的依賴加載完畢,計算出模塊定義函數正確調用順序,然後依次調用它們。

data-main

<script data-main="scripts/main" src="scripts/require.js"></script>
<script src="scripts/other.js"></script>

data-main指定的文件是異步加載的,所以不能保證main.js文件在other.js文件加載前完成加載。

模塊定義與加載

下面是一個最基本的demo:

// zoo.js
define(['./lion','./tiger'], function(lion, tiger){
    var perform = function () {
        console.log('馬戲團開演了');
        lion.perform();
        tiger.perform();
    }
    return {
        perform: perform
    }
})
// main.js
require(['./zoo'], function(zoo){
    zoo.perform();
})

requirejs也支持簡單的鍵值對形式

define({
        color: "black",
        size: "unisize"
});

RequireJS的模塊語法允許它儘快地加載多個模塊,雖然加載的順序不定,但依賴的順序最終是正確的。同時因爲無需創建全局變量,甚至可以做到在同一個頁面上同時加載同一模塊的不同版本。

嚴重不鼓勵模塊定義全局變量。遵循此處的定義模式,可以使得同一模塊的不同版本並存於同一個頁面上(參見 高級用法 )。另外,函參的順序應與依賴順序保存一致。

一個文件對應一個模塊,但你可以使用優化工具,爲每個模塊生成模塊名以將多個模塊打成一個包,加快到瀏覽器的載人速度。

循環依賴

如果你定義了一個循環依賴(a依賴b,b同時依賴a),則在這種情形下當b的模塊函數被調用的時候,它會得到一個undefined的a。b可以在模塊已經定義好後用require()方法再獲取(記得將require作爲依賴注入進來):

//Inside b.js:
define(["require", "a"], function(require,  a) {
        //"a" in this case will be null if a also asked for b,
        //a circular dependency.
        return function(title) {
            return require("a").doSomething();
        }
    }
);

一般說來你無需使用require()去獲取一個模塊,而是應當使用注入到模塊函數參數中的依賴。循環依賴比較罕見,它也是一個重構代碼重新設計的警示燈。你也可以換成commonjs風格的代碼

define(function(require, exports, module) {
    var a = require("a");
    exports.foo = function () {
        return a.bar();
    };
});

JSONP

require(["http://example.com/api/data.json?callback=define"],
    function (data) {
        //The data object will be the API response for the
        //JSONP data call.
        console.log(data);
    }
);

配置選項

配置方式

<script src="scripts/require.js"></script>
<script>
  require.config({
    baseUrl: "/another/path",
    paths: {
        "some": "some/v1.0"
    },
    waitSeconds: 15
  });
  require( ["some/module", "my/module", "a.js", "b.js"],
    function(someModule,    myModule) {
    }
  );
</script>

下面主要對幾個require.config支持的配置項進行闡述

paths

設置path時起始位置是相對於baseUrl的,除非該path設置以”/”開頭或含有URL協議(如http:)。此外,paths還可以進行備錯處理:

    jquery: [
        'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min',
        //If the CDN location fails, load from this location
        'lib/jquery'
    ]

shim

爲那些沒有使用define()來聲明依賴關係、設置模塊的”瀏覽器全局變量注入”型腳本做依賴和導出配置。注意:設置shim本身不會觸發代碼的加載。例如:

shim: {
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone',
            init: function (bar) {
               // 解決一些庫的衝突
           }
        },
}

map

map用來對項目進行一些列加載的版本控制,如下代碼,當“some/newmodule”調用了“require(‘foo’)”,它將獲取到foo1.2.js文件;而當“some/oldmodule”調用“`require(‘foo’)”時它將獲取到foo1.0.js,而其他模塊調用foo時,則會獲得foo.0.9。請在map配置中僅使用絕對模塊ID,“../some/thing”之類的相對ID不能工作。

map: {
    '*':{
       'foo':'foo 0.9'
    }
    'some/newmodule': {
        'foo': 'foo1.2'
    },
    'some/oldmodule': {
        'foo': 'foo1.0'
    }
}

config

設置想要傳遞給具體模塊的信息

requirejs.config({
    config: {
        'bar': {
            size: 'large'
        },
        'baz': {
            color: 'blue'
        }
    }
});

define(['module'], function (module) {
//Will be the value 'blue'
var color = module.config().color;
});

waitSeconds

在放棄加載一個腳本之前等待的秒數。設爲0禁用等待超時。默認爲7秒。

插件

domReady是最常見的插件,其作用是保證模塊腳本執行之前頁面已經完成加載。模塊實現了Loader Plugin API,因此你可以使用loader plugin語法(注意domReady依賴的!前綴)來強制require()回調函數在執行之前等待DOM Ready。當用作loader plugin時,domReady會返回當前的document

require(['domReady!'], function (doc) {
});

注意: 如果document需要一段時間來加載(也許是因爲頁面較大,或加載了較大的js腳本阻塞了DOM計算),使用domReady作爲loader plugin可能會導致RequireJS“超時”錯。如果這是個問題,則考慮增加waitSeconds配置項的值,或在require()使用domReady()調用(將其當做是一個模塊)。

AMD VS CMD比較

AMD 是 RequireJS 在推廣過程中對模塊定義的規範化產出。CMD 是 SeaJS 在推廣過程中對模塊定義的規範化產出。

二者推崇的代碼風格不同,CMD 推崇依賴就近,AMD 推崇依賴前置

對於依賴的模塊,AMD 是提前執行,CMD 是延遲執行

二者最大的區別在於factory回調的執行時機不同

/* a.js */
define(factory);
/* b.js */
define(factory);
/* c.js */
define(function(require) {
 // BEGIN
  if(some_condition) {
    require('./a').doSomething();
  } else {
    require('./b').soSomething();
  }
  // END
});

在AMD模式下,c模塊的 factory 在執行時,會接收 a 和 b 兩個參數。這意味着,c 依賴的所有模塊,都是在一開始就得執行好,即便有可能不需要執行。,換句話說,在 BEGIN 處,a 和 b 的 factory 都已經執行好。在 CMD 規範裏,在 BEGIN 處,a 和 b 的 factory 還沒未執行,在 END 處時,根據條件,只會執行其中一個。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章