模塊化 目前比較流行的 JS 模塊化方案有 CommonJS、AMD、CMD 以及 ES6 Module,還有個 UMD 方案。 CommonJS CommonJS 是伺服器端的模塊化方案,nodeJs 就採用了這種方案。在 CommonJS 規範中,一個文件即一個模塊,用 和`exports re ...
模塊化
目前比較流行的 JS 模塊化方案有 CommonJS、AMD、CMD 以及 ES6 Module,還有個 UMD 方案。
CommonJS
CommonJS 是伺服器端的模塊化方案,nodeJs 就採用了這種方案。在 CommonJS 規範中,一個文件即一個模塊,用module.exports
和exports
定義模塊輸出的介面,用require
載入模塊。
// math.js
function add(a, b) {
return a+b;
}
module.exports = {
add: add
}
// 也可以這樣寫
exports.add = (a, b) => a+b;
// main.js
const math = require('./math');
math.add(1, 2); // 3
CommonJs
採用同步載入方式,對於伺服器和本地環境來說,同步載入是非常快的,但對於瀏覽器來說,就不行了,限於網路因素,非同步載入才是比較好的方案。
AMD
為瞭解決瀏覽器模塊化的問題,AMD 和 CMD 這兩個非同步載入方案被提出,requireJS
可以說是 AMD 方案的最佳實踐。
// index.html
// before
<script src="..."></script>
<script src="..."></script>
<script src="..."></script>
// AMD - requireJS
<script src="require.js" data-main="main.js"></script>
在 requireJS 中用define
定義模塊,require
載入模塊,require.config
用來配置路徑。
// math.js
// define(id?, dependencies?, factory)
define(() => {
return {
add: (a, b) => a+b
}
});
// main.js
require.config({
baseUrl: 'js/lib',
paths: {
// 左邊是模塊ID,右邊是路徑
// 這裡的路徑是 js/lib/jquery.js
jquery: 'jquery',
// 這裡的路徑是 js/lib/math.js
math: 'math'
}
});
// require([modlue], callback)
require(['jquery', 'math'], ($, math) => {
// do something
});
需要註意的是,AMD 方案是依賴前置的,提前執行。
// AMD 依賴前置,提前執行
define(['a', 'b'], function(a, b){
// a 和 b誰先載入完,誰就先執行
// 並不按照代碼順序同步執行
a.doSomething();
b.doSomething();
})
官網:requireJS
CMD
與 AMD 不同,CMD 是依賴就近,延遲執行,requireJS 也支持 CMD 寫法。
// CMD 依賴就近,延遲執行
define(function(require, exports, module){
// 需要 a 時,才執行 a
var a = require("a");
a.doSomething();
// 需要 b 時,才執行 b
var b = require("b");
b.doSomething()
})
阿裡的 seaJS 可以說是比較出名的 CMD 實例項目,但現在都有更好的方案來替代它們了。
需要註意的是,兩者只是對依賴模塊的執行時機不一樣,並非載入時機不一樣,模塊的載入時機都是一樣的,它們都是非同步載入的。AMD是模塊載入完就會執行模塊,所有模塊都載入執行完就進入require
的回調函數,CMD 則是所有模塊都載入完,但不執行,等到require
這個模塊時才執行。
CMD標準:https://github.com/cmdjs
AMD標準:https://github.com/amdjs
UMD
UMD 是 CommonJS 和 AMD 的混合,它會判斷當前環境支持哪個就使用哪個,這個就不多說了。
ES6 Module
ES6 Module 橫空出世,混亂的時代結束了。
ES6 在語言標準層面定義模塊化規範,而且簡潔明瞭,完全可以取代 CommonJS 和 AMD 規範,成為瀏覽器和伺服器通用的模塊解決方案。
ES6 Module 主要使用export
輸出,import
載入。
// math.js
let PI = 3.1415;
let circleArea = (x) => x*PI;
export {PI, circleArea};
// 也可以這樣寫
export let PI = 3.1415;
export let circleArea = (x) => x*PI;
// main.js
import {PI, circleArea} from './math.js'
circleArea(1); // 3.1415
// 也可以這樣寫
import * as math from './math.js'
math.circleArea(1); // 3.1415
還有一個export default
命令:
// 使用 export default 輸出的模塊只能有一個輸出
export default () => concole.log('hhh');
需要註意的是,CommonJS 輸出的是值的拷貝,對於基本數據類型是直接拷貝,即緩存模塊,但複雜數據類型是淺拷貝,因此修改一個模塊中的值,是會改變另一個模塊的值的。 require
模塊的時候,就會執行整個模塊,即運行時載入,然後exports
輸出,緩存輸出值,再次require
時,會直接在緩存內取值。
而 ES6 Module 是編譯時載入,import
載入的是值的引用,載入進來的值是不能被修改的,即值是只讀的,而且不論是基本數據類型還是複雜數據類型,修改原模塊的值修改,import
得到的值也是會改變,即值是動態的。
- CommonJS 輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
- CommonJS 是運行時載入,ES6 模塊是編譯時輸出介面。
至於迴圈載入問題(a 依賴 b,b 依賴 c,c 依賴 a):
CommonJS 迴圈載入時,因為是屬於載入時執行,所以一旦出現某個模塊被"迴圈載入",就只輸出已經執行的部分,還未執行的部分就不輸出。而對於 ES6 Module來說,因為是動態引用,所以出現迴圈載入時,只要兩個模塊之間存在某個引用,即取值的時候能夠真正的取到值,代碼就能夠執行。
// node 環境
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar());
function foo() { return 'foo' }
export {foo};
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo());
function bar() { return 'bar' }
export {bar};
/* 結果
b.mjs
foo
a.mjs
bar
*/
執行a.mjs
不會報錯,因為函數聲明會提升,在執行import {bar} from './b'
時,函數foo
就已經有定義了,所以b.mjs
載入的時候不會報錯。這也意味著,如果把函數foo
改寫成函數表達式,也會報錯。
備註
規範真多,但我也只用過 CommonJS 和 ES6 Module,而且 ES6 還要靠 babel。