1. 程式人生 > >前端模塊化(六):CMD規範

前端模塊化(六):CMD規範

cati end dos 屬性 resolv 代碼 滿足 urn target

1 概述

CMD(Common Module Definition)是國內大牛玉伯在開發SeaJS的時候提出來的,屬於CommonJS的一種規範,根據瀏覽器的異步環境做了自己的實現。它和 AMD 很相似,盡量保持簡單,並與 CommonJS 和 Node.js 的 Modules 規範保持了很大的兼容性。

2 define方法:定義模塊

在CMD中,一個模塊就是一個文件,格式為:define( factory )define 是一個全局函數,用來定義模塊。

2.1 factory 函數

define 接受 factory 參數,factory 可以是一個函數,也可以是一個對象或字符串。

factory 為對象、字符串時,表示模塊的接口就是該對象、字符串。比如可以如下定義一個 JSON 數據模塊:

define({ "foo": "bar" });

也可以通過字符串定義模板模塊:

define(‘I am a template. My name is {{name}}.‘);

factory 為函數時,表示是模塊的構造方法。執行該構造方法,可以得到模塊向外提供的接口。factory方法在執行時,默認會傳入三個參數:require、exports 和 module:

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

define也可以接受兩個以上的參數,字符串id為模塊標識,數組deps為模塊依賴,格式為:define( id?, deps?, factory );具體使用如下:

define(‘hello‘, [‘jquery‘], function(require, exports, module) {
  // 模塊代碼
});

但是,帶 id 和 deps 參數的 define 用法不屬於 CMD 規範。CMD推崇一個文件一個模塊,所以經常就用文件名作為模塊id。CMD推崇依賴就近,所以一般不在define的參數中寫依賴,而在factory中寫。

2.2 define.cmd 屬性

defin.cmd屬性是一個空對象,可用來判定當前頁面是否有 CMD 模塊加載器,其用法跟AMD的denfine.amd相似,寫法如下:

if (typeof define === "function" && define.cmd) {
  // 有 Sea.js 等 CMD 模塊加載器存在
}

3 require方法:加載模塊

require 是 factory 函數的第一個參數。require 是一個方法,接受 模塊標識 作為唯一參數,用來獲取其他模塊提供的接口。

define(function(require, exports) {

  // 獲取模塊 a 的接口

  var a = require(‘./a‘);

  // 調用模塊 a 的方法

  a.doSomething();

});

3.1 require.async (id, callback)

require.async 方法用來在模塊內部異步加載模塊,並在加載完成後執行指定回調。callback 參數可選。

define(function(require, exports, module) {  

  // 異步加載模塊,在加載完成時,執行回調

  require.async([‘./c‘, ‘./d‘], function(c, d) {

    c.doSomething();

    d.doSomething();

  });

});

require 是同步往下執行,require.async 則是異步回調執行。require.async 一般用來加載可延遲異步加載的模塊。

define(function(require, exports, module) {  

  // 異步加載模塊,在加載完成時,執行回調

  require.async([‘./c‘, ‘./d‘], function(c, d) {

    c.doSomething();

    d.doSomething();

  });

});

3.2 require.resolve(id)

使用模塊系統內部的路徑解析機制來解析並返回模塊路徑。該函數不會加載模塊,只返回解析後的絕對路徑。

define(function(require, exports) {

  console.log(require.resolve(‘./b‘));

  // ==> http://example.com/path/to/b.js

});

這可以用來獲取模塊路徑,一般用在插件環境或需動態拼接模塊路徑的場景下。

4 exports對象:暴露接口對象

exports 是一個對象,用來向外提供模塊接口。

define(function(require, exports) {

  // 對外提供 foo 屬性

  exports.foo = ‘bar‘;

  // 對外提供 doSomething 方法

  exports.doSomething = function() {};

});

除了給 exports 對象增加成員,還可以使用 return 直接向外提供接口。

define(function(require) {

  // 通過 return 直接提供接口

  return {

    foo: ‘bar‘,

    doSomething: function() {}

  };

});

需要特別註的是,不能直接給 exports 賦值,例如下面是錯誤的寫法:

define(function(require, exports) {

  // 錯誤用法!!!

  exports = {

    foo: ‘bar‘,

    doSomething: function() {}

  };

});

exports 僅僅是 module.exports 的一個引用。而模塊導出的時候,真正導出的是module.exports,而不是exports。在 factory 內部給 exports 重新賦值時, exports 就不再指向module.exports 了。因此給 exports 賦值是無效的,正確的寫法是用 return 或者給 module.exports 賦值:

define(function(require, exports, module) {

  // 正確寫法

  module.exports = {

    foo: ‘bar‘,

    doSomething: function() {}

  };

});

5 module對象:本模塊對象

module 是一個對象,上面存儲了與當前模塊相關聯的一些屬性和方法。

5.1 module.id

模塊的唯一標識。

define(‘id‘, [], function(require, exports, module) {

  // 模塊代碼

});

上面代碼中,define 的第一個參數就是模塊標識。

5.2 module.uri

根據模塊系統的路徑解析規則得到的模塊絕對路徑。

define(function(require, exports, module) {

  console.log(module.uri); 

  // ==> http://example.com/path/to/this/file.js

});

一般情況下(沒有在 define 中手寫 id 參數時),module.id 的值就是 module.uri,兩者完全相同。

5.3 module.dependencies

dependencies 是一個數組,表示當前模塊的依賴。

5.4 module.exports

表示當前模塊對外輸出的接口,其他文件加載該模塊,實際上就是讀取module.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 的賦值需要同步執行,不能放在回調函數裏。下面這樣是不行的:

// x.js

define(function(require, exports, module) {

  // 錯誤用法

  setTimeout(function() {

    module.exports = { a: "hello" };

  }, 0);

});

在 y.js 裏有調用到上面的 x.js:

// y.js

define(function(require, exports, module) {

  var x = require(‘./x‘);

  // 無法立刻得到模塊 x 的屬性 a

  console.log(x.a); // undefined

});

小結

這就是 CMD 模塊定義規範的所有內容。下一篇我們開始介紹CMD規範的產物SeaJs加載器,進一步了解CMD規範的具體實現。

參考鏈接

[1] https://github.com/seajs/seajs/issues/242

前端模塊化(六):CMD規範