AMD , CMD, CommonJS,ES Module,UMD

darlyn發表於2018-08-22

JavaScript模組化

規範JavaScript的模組定義和載入機制,降低了學習和使用各種框架的門檻,能夠以一種統一的方式去定義和使用模組,提高開發效率,降低了應用維護成本。

  • 命名衝突
  • 檔案依賴

差異

  • AMD 與 CMD:
    • AMD是 RequireJS 在推廣過程中對模組定義的規範化產出。
    • CMD是 SeaJS 在推廣過程中對模組定義的規範化產出。
    • CMD推崇依賴就近,AMD推崇依賴前置。
  • ES Module與CommonJS:
    • CommonJS模組是物件,是執行時載入,執行時才把模組掛載在exports之上(載入整個模組的所有),載入模組其實就是查詢物件屬性。
    • ES Module不是物件,是使用export顯示指定輸出,再通過import輸入。此法為編譯時載入,編譯時遇到import就會生成一個只讀引用。等到執行時就會根據此引用去被載入的模組取值。所以不會載入模組所有方法,僅取所需。
    • CommonJS 模組輸出的是一個值的拷貝,ES6 模組輸出的是值的引用。
    • CommonJS 模組是執行時載入,ES6 模組是編譯時輸出介面
  • CommonJS與AMD/CMD:
    • AMD/CMD是CommonJS在瀏覽器端的解決方案。
    • CommonJS是同步載入(程式碼在本地,載入時間基本等於硬碟讀取時間)。
    • AMD/CMD是非同步載入(瀏覽器必須這麼做,程式碼在服務端)
  • UMD與AMD/CMD
    • UMD(Universal Module Definition)是AMD和CommonJS的糅合,跨平臺的解決方案。
    • AMD模組以瀏覽器第一的原則發展,非同步載入模組。
    • CommonJS模組以伺服器第一原則發展,選擇同步載入,它的模組無需包裝(unwrapped modules)。
    • UMD先判斷是否支援Node.js的模組(exports)是否存在,存在則使用Node.js模組模式。再判斷是否支援AMD(define是否存在),存在則使用AMD方式載入模組。

用法

  • CommonJS
    • 匯出使用module.exports,也可以exports。就是在此物件上掛屬性。exports指向module.exports,即exports= module.exports
    • 載入模組使用require('xxx')。相對、絕對路徑均可。預設引用js,可以不寫.js字尾
// commonjs
module.exports.add = function add(params) {
    return ++params;
}
exports.sub = function sub(params) {
    return --params;
}
// index.js
var common = require('./commonjs');
console.log(common.sub(1));
console.log(common.add(1));
複製程式碼
  • AMD/RequireJS
    • 定義模組:define(id?, dependencies?, factory)
      • 依賴有三個預設的,即"require", "exports", "module"。順序個數均可視情況
      • 如果忽略則factory預設此三個傳入引數
      • id一般是不傳的,預設是檔名
    • 載入模組:require([module], factory)
// a.js
define(["b", "require", "exports"], function(b, require, exports) {
    console.log("a.js執行");
    console.log(b);
    // 暴露api可以使用exports、module.exports、return
    exports.a = function() {
        return require("b");
    }
})
// b.js
define(function() {
    console.log('b.js執行');
    console.log(require);
    console.log(exports);
    console.log(module);
    return 'b';
})
// index.js
// 支援Modules/Wrappings寫法,注意dependencies得是空的,且factory引數不可空
define(function(require, exports, module) {
    console.log('index.js執行');
    var a = require('a');
    var b = require('b');
})
// index.js
require(['a', 'b'], function(a, b) {
    console.log('index.js執行');
})
複製程式碼
  • CMD/SeaJS
    • 定義模組:define(factory)
      • require, exports, module引數順序不可亂
      • 暴露api方法可以使用exports、module.exports、return
      • 與requirejs不同的是,若是未暴露,則返回{},requirejs返回undefined
    • 載入模組:require
    • 定義模組無需列依賴,它會呼叫factory的toString方法對其進行正則匹配以此分析依賴
    • 預先下載,延遲執行
// a.js
define(function(require, exports, module) {
    console.log('a.js執行');
    console.log(require);
    console.log(exports);
    console.log(module);
})
// b.js
define(function(require, module, exports) {
    console.log('b.js執行');
    console.log(require);
    console.log(exports);
    console.log(module);
})
// index.js
define(function(require) {
    var a = require('a');
    var b = require('b');
    console.log(a);
    console.log(b);
})
複製程式碼
  • ES Module
    • 輸出/export
    • 輸入/import
    • 輸入的模組變數是不可重新賦值的,它只是個可讀引用,不過卻可以改寫屬性
// 報錯1
export 1;
// 報錯2
const m = 1;
export m;

// 介面名與模組內部變數之間,建立了一一對應的關係
// 寫法1
export const m = 1;
// 寫法2
const m = 1;
export { m };
// 寫法3
const m = 1;
export { m as module };
複製程式碼
// 類似於物件解構
// module.js
export const m = 1;
// index.js
// 注意,這裡的m得和被載入的模組輸出的介面名對應
import { m } from './module';
// 若是想為輸入的變數取名
import { m as m1 }  './module';
// 值得注意的是,import是編譯階段,所以不能動態載入,比如下面寫法是錯誤的。因為'a' + 'b'在執行階段才能取到值,執行階段在編譯階段之後
import { 'a' + 'b' } from './module';
// 若是隻是想執行被載入的模組,如下
// 值得注意的是,即使載入兩次也只是執行一次
import './module';
// 整體載入
import * as module from './module';
複製程式碼
  • 總結:
區別項 es模組化 commonJS AMD
可用於服務端還是瀏覽器 服務端和瀏覽器 服務端 瀏覽器
模組依賴關係何時確定(即:何時載入模組) 編譯時 執行時 執行時
設計思想 儘量的靜態化
模組是不是物件 不是
是否整體載入模組(即載入的所有方法)
是否是動態更新(即通過介面,可以取到模組內部實時的值) 是。es module輸出的是值的引用 不是。commonJS模組輸出的是值的拷貝,不存在動態更新
模組變數是否是隻讀的 v是。原因:ES6 輸入的模組變數,只是一個“符號連線”,所以這個變數是隻讀的,對它進行重新賦值會報錯。
  • commonJS模組就是物件,整體載入模組(即載入的所有方法)

  • ES6 模組不是物件,而是通過export命令顯式指定輸出的程式碼,再通過import命令輸入。

  • export命令規定的是對外的介面,必須與模組內部的變數建立一一對應關係

  • export語句輸出的介面,與其對應的值是動態繫結關係,即通過該介面,可以取到模組內部實時

  • export命令和import命令可以出現在模組的任何位置,只要處於模組頂層就可以。 如果處於塊級作用域內,就會報錯,這是因為處於條件程式碼塊之中,就沒法做靜態優化了,違背了ES6模組的設計初衷。

  • import命令具有提升效果,會提升到整個模組的頭部,首先執行。

相關文章