前端模組化(CommonJs,AMD和CMD)

linwalker發表於2017-03-22

  前端模組規範有三種:CommonJs,AMD和CMD。

  CommonJs用在伺服器端,AMD和CMD用在瀏覽器環境。

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

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

  AMD:提前執行(非同步載入:依賴先執行)+延遲執行

  CMD:延遲執行(執行到需載入,根據順序執行)

 

 模組

  函式寫法

function f1(){
    //...
}
function f2(){
    //...
}

  模組就是實現特定功能的檔案,把幾個函式放在一個檔案裡就構成了一個模組。需要的時候載入這個檔案,呼叫其中的函式即可。

  但這樣做會汙染全域性變數,無法保證不與其他模組發生變數名衝突,而且模組成員之間沒什麼關係。

  物件寫法


var module = {
  star : 0,
  f1 : function (){
    //...
  },
  f2 : function (){
    //...
  }
};
module.f1();
module.star = 1;

  模組寫成一個物件,模組成員都封裝在物件裡,通過呼叫物件屬性,訪問使用模組成員。但同時也暴露了模組成員,外部可以修改模組內部狀態。

  立即執行函式


var module = (function(){
    var star = 0;
    var f1 = function (){
      console.log('ok');
    };
    var f2 = function (){
      //...
    };
       return {
          f1:f1,
          f2:f2
       };
  })();
module.f1();  //ok
console.log(module.star)  //undefined

  外部無法訪問內部私有變數

 CommonJs

  CommonJS是伺服器端模組的規範,由Node推廣使用。由於服務端程式設計的複雜性,如果沒有模組很難與作業系統及其他應用程式互動。使用方法如下:


math.js
exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
      sum += args[i++];
    }
    return sum;
};

increment.js
var add = require('math').add;
exports.increment = function(val) {
    return add(val, 1);
};

index.js
var increment = require('increment').increment;
var a = increment(1); //2

  根據CommonJS規範:

  • 一個單獨的檔案就是一個模組。每一個模組都是一個單獨的作用域,也就是說,在該模組內部定義的變數,無法被其他模組讀取,除非定義為global物件的屬性。
  • 輸出模組變數的最好方法是使用module.exports物件。

  • 載入模組使用require方法,該方法讀取一個檔案並執行,返回檔案內部的module.exports物件

  仔細看上面的程式碼,您會注意到 require 是同步的。模組系統需要同步讀取模組檔案內容,並編譯執行以得到模組介面。

  然而, 這在瀏覽器端問題多多。

  瀏覽器端,載入 JavaScript 最佳、最容易的方式是在 document 中插入<script>標籤。但指令碼標籤天生非同步,傳統 CommonJS 模組在瀏覽器環境中無法正常載入。

  解決思路之一是,開發一個伺服器端元件,對模組程式碼作靜態分析,將模組與它的依賴列表一起返回給瀏覽器端。 這很好使,但需要伺服器安裝額外的元件,並因此要調整一系列底層架構。

  另一種解決思路是,用一套標準模板來封裝模組定義:


define(function(require, exports, module) {

  // The module code goes here

});

  這套模板程式碼為模組載入器提供了機會,使其能在模組程式碼執行之前,對模組程式碼進行靜態分析,並動態生成依賴列表。


math.js
define(function(require, exports, module) {
  exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
      sum += args[i++];
    }
    return sum;
  };
});

increment.js
define(function(require, exports, module) {
  var add = require('math').add;
  exports.increment = function(val) {
    return add(val, 1);
  };
});

index.js
define(function(require, exports, module) {
  var inc = require('increment').increment;
  inc(1); // 2
});

 AMD

  AMD是"Asynchronous Module Definition"的縮寫,意思就是"非同步模組定義"。由於不是JavaScript原生支援,使用AMD規範進行頁面開發需要用到對應的庫函式,也就是大名鼎鼎RequireJS,實際上AMD 是 RequireJS 在推廣過程中對模組定義的規範化的產出

  它採用非同步方式載入模組,模組的載入不影響它後面語句的執行。所有依賴這個模組的語句,都定義在一個回撥函式中,等到載入完成之後,這個回撥函式才會執行。

  RequireJS主要解決兩個問題

  • 多個js檔案可能有依賴關係,被依賴的檔案需要早於依賴它的檔案載入到瀏覽器
  • js載入的時候瀏覽器會停止頁面渲染,載入檔案越多,頁面失去響應時間越長

  RequireJs也採用require()語句載入模組,但是不同於CommonJS,它要求兩個引數:

  第一個引數[module],是一個陣列,裡面的成員就是要載入的模組;第二個引數callback,則是載入成功之後的回撥函式。math.add()與math模組載入不是同步的,瀏覽器不會發生假死。


require([module], callback);

require([increment'], function (increment) {
    increment.add(1);
});

  define()函式

  RequireJS定義了一個函式 define,它是全域性變數,用來定義模組:

  define(id?, dependencies?, factory);

  引數說明:

  • id:指定義中模組的名字,可選;如果沒有提供該引數,模組的名字應該預設為模組載入器請求的指定指令碼的名字。如果提供了該引數,模組名必須是“頂級”的和絕對的(不允許相對名字)。

  • 依賴dependencies:是一個當前模組依賴的,已被模組定義的模組標識的陣列字面量。
    依賴引數是可選的,如果忽略此引數,它應該預設為["require", "exports", "module"]。然而,如果工廠方法的長度屬性小於3,載入器會選擇以函式的長度屬性指定的引數個數呼叫工廠方法。

  • 工廠方法factory,模組初始化要執行的函式或物件。如果為函式,它應該只被執行一次。如果是物件,此物件應該為模組的輸出值。

  來舉個例子看看:


define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
       exports.verb = function() {
           return beta.verb();
           //Or:
           return require("beta").verb();
       }
   });

  RequireJs使用例子

  require.config是用來定義別名的,在paths屬性下配置別名。然後通過requirejs(引數一,引數二);引數一是陣列,傳入我們需要引用的模組名,第二個引數是個回撥函式,回撥函式傳入一個變數,代替剛才所引入的模組。


main.js
//別名配置
requirejs.config({
    paths: {
        jquery: 'jquery.min' //可以省略.js
    }
});
//引入模組,用變數$表示jquery模組
requirejs(['jquery'], function ($) {
    $('body').css('background-color','red');
});

  引入模組也可以只寫require()。requirejs通過define()定義模組,定義的引數上同。在此模組內的方法和變數外部是無法訪問的,只有通過return返回才行.


math.js
define('math',['jquery'], function ($) {//引入jQuery模組
    return {
        add: function(x,y){
            return x + y;
        }
    };
});

  將該模組命名為math.js儲存。


require(['jquery','math'], function ($,math) {
    console.log(math.add(10,100));//110
});

  main.js引入模組方法

 CMD

  CMD 即Common Module Definition通用模組定義,CMD規範是國內發展出來的,就像AMD有個requireJS,CMD有個瀏覽器的實現SeaJS,SeaJS要解決的問題和requireJS一樣,只不過在模組定義方式和模組載入(可以說執行、解析)時機上有所不同。

  在 CMD 規範中,一個模組就是一個檔案。程式碼的書寫格式如下:


define(function(require, exports, module) {

  // 模組程式碼

});

  require是可以把其他模組匯入進來的一個引數;而exports是可以把模組內的一些屬性和方法匯出的;module 是一個物件,上面儲存了與當前模組相關聯的一些屬性和方法。

  AMD是依賴關係前置,在定義模組的時候就要宣告其依賴的模組;

  CMD是按需載入依賴就近,只有在用到某個模組的時候再去require:


// 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()
  ...
})

  seajs使用例子


// 定義模組  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
  exports.data = 1;
});

// 載入模組
seajs.use(['myModule.js'], function(my){
    var star= my.data;
    console.log(star);  //1
});

 參考

  這篇《前端模組化:CommonJs,AMD和CDM》主要是個人對以下文章的總結,感謝這些老司機們的分享。

  前端模組化

  詳解JavaScript模組化開發

  Javascript模組化程式設計

  從 CommonJS 到 Sea.js

相關文章