JavaScript模塊化CommonJS/AMD/CMD/UMD/ES6Module的區別

JS-模塊化進程

隨着js技術的不斷髮展,途中會遇到各類問題,好比模塊化。node

那什麼是模塊化呢,他們的目的是什麼?webpack

定義:如何把一段代碼封裝成一個有用的單元,以及如何註冊此模塊的能力、輸出的值
依賴引用:如何引用其它代碼單元git

到目前爲止,大概分爲如下幾個里程碑式節點。es6

原始的開發方式 ---> CommonJS ---> AMD ---> CMD ---> UMD ---> ES6Module

原始的開發方式

最開始的時候,JS自身是沒有模塊機制的。項目有多個js文件。github

// a.js
function foo() {}
// b.js
function bar() {}
// c.js
foo()

HTML加載web

<script src="a.js"></script>
<script src="b.js"></script>
<script src="c.js"></script>

原始的開發方式,隨着項目的複雜度,代碼量愈來愈多,所須要加載的文件也愈來愈多,這個時候,就要考慮幾個問題了:api

  1. 命名問題:全部文件的方法都是掛載到global上,會污染全局環境,而且須要考慮命名衝突問題。
  2. 依賴問題:script是順序加載的,若是各文件之間有依賴,那咱們得考慮加載.js文件的書寫順序。
  3. 網絡問題。若是文件過多,所需請求次數會增多,增長加載時間。

CommonJS && node.js

CommonJS規範 CommonJS使用例子
CommonJS規範,主要運行於服務器端,同步加載模塊,而加載的文件資源大多數在本地服務器,因此執行速度或時間沒問題。Node.js很好的實現了該規範。
該規範指出,一個單獨的文件就是一個模塊。
模塊功能主要的幾個命令:requiremodule.exportsrequire命令用於輸入其餘模塊提供的功能,module.exports命令用於規範模塊的對外接口,輸出的是一個值的拷貝,輸出以後就不能改變了,會緩存起來。瀏覽器

// moduleA.js
var name = 'weiqinl'
function foo() {}

module.exports = exports = {
name,
foo
}

// moduleB.js
var ma = require('./moduleA') // 能夠省略後綴.js
exports.bar = function() {
ma.name === 'weiqinl' // true
ma.foo() // 執行foo方法
}

// moduleC.js
var mb = require('./moduleB')
mb.bar()

經過例子,咱們能夠看出require(moduleId)來加載其餘模塊的內容,其返回值就是其引用的外部模塊所暴露的API,以後再經過module.exports或者exports來爲當前模塊的方法和變量提供輸出接口。緩存

最後經過node來執行模塊。

AMD && Require.js

AMD規範文檔 AMD使用例子
AMD(Asynchronous Module Definition - 異步加載模塊定義)規範,制定了定義模塊的規則,一個單獨的文件就是一個模塊,模塊和模塊的依賴能夠被異步加載。主要運行於瀏覽器端,這和瀏覽器的異步加載模塊的環境恰好適應,它不會影響後面語句的運行。該規範是在RequireJs的推廣過程當中逐漸完善的。

模塊功能主要的幾個命令:definerequirereturndefine.amddefine是全局函數,用來定義模塊,define(id?, dependencies?, factory)require命令用於輸入其餘模塊提供的功能,return命令用於規範模塊的對外接口,define.amd屬性是一個對象,此屬性的存在來代表函數遵循AMD規範。

// moduleA.js
define(['jQuery','lodash'], function($, _) {
var name = 'weiqinl',
function foo() {}
return {
name,
foo
}
})

// index.js
require(['moduleA'], function(a) {
a.name === 'weiqinl' // true
a.foo() // 執行A模塊中的foo函數
// do sth...
})

// index.html
<script src="js/require.js" data-main="js/index"></script>

在這裏,咱們使用define來定義模塊,return來輸出接口, require來加載模塊,這是AMD官方推薦用法。固然也可使用其餘兼容性的寫法,好比對 Simplified CommonJS Wrapper 格式的支持,但背後仍是原始AMD的運行邏輯。
AMD的運行邏輯是:提早加載,提早執行。在Requirejs中,申明依賴模塊時,會第一時間加載並執行模塊內的代碼,使後面的回調函數能在所需的環境中運行。
爲了更好地優化請求,同時推出了打包工具r.js,使所需加載的文件數減小。require.js模塊化開發,並用r.js打包例子

CMD && Sea.js

CMD規範文檔 CMD使用例子
CMD(Common Module Definition - 通用模塊定義)規範主要是Sea.js推廣中造成的,一個文件就是一個模塊,能夠像Node.js通常書寫模塊代碼。主要在瀏覽器中運行,固然也能夠在Node.js中運行。

// moduleA.js
// 定義模塊
define(function(require, exports, module) {
var func = function() {
var a = require('./a') // 到此纔會加載a模塊
a.func()
if(false) {
var b = require('./b') // 到此纔會加載b模塊
b.func()
}
}
// do sth...
exports.func = func;
})

// index.js
// 加載使用模塊
seajs.use('moduleA.js', function(ma) {
var ma = math.func()
})

// HTML,須要在頁面中引入sea.js文件。
<script src="./js/sea.js"></script>
<script src="./js/index.js"></script>

這裏define是一個全局函數,用來定義模塊,並經過exports向外提供接口。以後,若是要使用某模塊,能夠經過require來獲取該模塊提供的接口。最後使用某個組件的時候,經過seajs.use()來調用。

  1. 經過exports暴露接口。這意味着不須要命名空間了,更不須要全局變量。
  2. 經過require引入依賴。這可讓依賴內置,咱們只須要關心當前模塊的依賴。關注度分離

CMD推崇依賴就近,延遲執行。在上面例子中,經過require引入的模塊,只有當程序運行到此處的時候,模塊纔會自動加載執行。

同時推出了spm(static package manager)的打包方式,據說支付寶的項目在使用。

UMD && webpack

UMD文檔
UMD(Universal Module Definition - 通用模塊定義)模式,該模式主要用來解決CommonJS模式和AMD模式代碼不能通用的問題,並同時還支持老式的全局變量規範。

// 使用Node, AMD 或 browser globals 模式建立模塊
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD模式. 註冊爲一個匿名函數
define(['b'], factory);
} else if (typeof module === 'object' && module.exports) {
// Node等類CommonJS的環境
module.exports = factory(require('b'));
} else {
// 瀏覽器全局變量 (root is window)
root.returnExports = factory(root.b);
}
}(typeof self !== 'undefined' ? self : this, function (b) {
// 以某種方式使用 b

//返回一個值來定義模塊導出。(便可以返回對象,也能夠返回函數)
return {};
}));
  1. 判斷define爲函數,而且是否存在define.amd,來判斷是否爲AMD規範,
  2. 判斷module是否爲一個對象,而且是否存在module.exports來判斷是否爲CommonJS規範
  3. 若是以上兩種都沒有,設定爲原始的代碼規範。

這種模式,一般會在webpack打包的時候用到。output.libraryTarget將模塊以哪一種規範的文件輸出。

ES6 Module && ES6

ES6模塊加載文檔 ES6 Module使用例子
在ECMAScript 2015版本出來以後,肯定了一種新的模塊加載方式,咱們稱之爲ES6 Module。它和前幾種方式有區別和相同點。

  1. 它由於是標準,因此將來不少瀏覽器會支持,能夠很方便的在瀏覽器中使用。
  2. 它同時兼容在node環境下運行。
  3. 模塊的導入導出,經過importexport來肯定。
  4. 能夠和Commonjs模塊混合使用。
  5. CommonJS輸出的是一個值的拷貝。ES6模塊輸出的是值的引用,加載的時候會作靜態優化。
  6. CommonJS模塊是運行時加載肯定輸出接口,ES6模塊是編譯時肯定輸出接口。

ES6模塊功能主要由兩個命令構成:importexportimport命令用於輸入其餘模塊提供的功能。export命令用於規範模塊的對外接口。

export的幾種用法

// 輸出變量
export var name = 'weiqinl'
export var year = '2018'

// 輸出一個對象(推薦)
var name = 'weiqinl'
var year = '2018'
export { name, year}


// 輸出函數或類
export function add(a, b) {
return a + b;
}

// export default 命令
export default function() {
console.log('foo')
}

import導入其餘模塊

// 正常命令
import { name, year } from './module.js' //後綴.js不能省略

// 若是遇到export default命令導出的模塊
import ed from './export-default.js'

模塊編輯好以後,它能夠以多種形式加載。

1. 瀏覽器加載

瀏覽器加載ES6模塊,使用<script>標籤,可是要加入type="module"屬性
外鏈js文件

<script type="module" src="index.js"></script>

也能夠內嵌在網頁中

<script type="module">
import utils from './utils.js';
// other code
</script>

對於加載外部模塊,須要注意:

  • 代碼是在模塊做用域之中運行,而不是在全局做用域運行。模塊內部的頂層變量,外部不可見。
  • 模塊腳本自動採用嚴格模式,無論有沒有聲明use strict
  • 模塊之中,可使用import命令加載其餘模塊(.js後綴不可省略,須要提供絕對 URL 或相對 URL),也可使用export命令輸出對外接口。
  • 模塊之中,頂層的this關鍵字返回undefined,而不是指向window。也就是說,在模塊頂層使用this關鍵字,是無心義的。
  • 同一個模塊若是加載屢次,將只執行一次。

2. Node加載

Node要求 ES6 模塊採用.mjs後綴文件名。也就是說,只要腳本文件裏面使用import或者export命令,就必須採用.mjs後綴名。
這個功能還在試驗階段。安裝Node V8.5.0或以上版本,要用--experimental-modules參數才能打開該功能。

$ node --experimental-modules my-app.mjs

Nodeimport命令只支持異步加載本地模塊(file:協議),不支持加載遠程模塊。

總結

以上便是,我對js模塊化概念,最主要的仍是他們輸入輸出的區別,理論結合實踐完成的簡單梳理。經過閱讀前輩的文章或者各類Issue,也大概瞭解了JS的歷史進程。以史爲鑑,能夠知興替。此時不斷成爲歷史,歷史終將定論。

參考

  1. 各模塊化使用的例子
  2. Require.js
  3. Sea.js
  4. UMD
  5. ES6 Module
  6. JavaScript模塊化七日談

[完]

相關文章
相關標籤/搜索