簡介
在很久以前,js只是簡單的作為瀏覽器的互動操作而存在,一般都是非常短小的指令碼,所以都是獨立存在的。
但是隨著現代瀏覽器的發展,特別是nodejs的出現,js可以做的事情變得越來越多也越來越複雜。於是我們就需要模組系統來組織不同用途的指令碼,進行邏輯的區分和引用。
今天將會給大家介紹一下js中的模組系統。
CommonJS和Nodejs
CommonJS是由Mozilla公司在2009年1月份提出來了。沒錯,就是那個firfox的公司。
最初的名字叫做ServerJS,在2009年8月的時候為了表示這個標準的通用性,改名為CommonJS。
CommonJS最主要的應用就是服務端的nodejs了。瀏覽器端是不直接支援CommonJS的,如果要在瀏覽器端使用,則需要進行轉換。
CommonJS使用require()來引入模組,使用module.exports來匯出模組。
我們看一個CommonJS的例子:
require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;
注意,CommonJS是同步載入的。
AMD非同步模組載入
AMD的全稱是Asynchronous Module Definition 。它提供了一個非同步載入模組的模式。
AMD是RequireJS在推廣過程中對模組定義的規範化產出。
非同步載入的好處就是可以在需要使用模組的時候再進行載入,從而減少了一次性全部載入的時間,尤其是在瀏覽器端,可以提升使用者的體驗。
看下AMD載入模組的定義:
define(id?, dependencies?, factory);
AMD是通過define來定義和載入依賴模組的。
其中id表示要定義的模組的名字,dependencies表示這個模組的依賴模組,factory是一個函式,用來初始化模組或者物件。
我們看一個例子:
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});
這個例子中,我們定義了一個alpha模組,這個模組需要依賴"require", "exports", "beta"三個模組。
並且在factory中匯出了beta模組的verb方法。
define中id和dependencies都不是必須的:
//無id
define(["alpha"], function (alpha) {
return {
verb: function(){
return alpha.verb() + 2;
}
};
});
//無依賴
define({
add: function(x, y){
return x + y;
}
});
甚至我們可以在AMD中使用CommonJS:
define(function (require, exports, module) {
var a = require('a'),
b = require('b');
exports.action = function () {};
});
定義完之後,AMD使用require來載入模組:
require([dependencies], function(){});
第一個引數是依賴模組,第二個引數是回撥函式,會在前面的依賴模組都載入完畢之後進行呼叫。載入的模組會以引數形式傳入該函式,從而在回撥函式內部就可以使用這些模組。
require(["module", "../file"], function(module, file) { /* ... */ });
require載入模組是非同步載入的,但是後面的回撥函式只會在所有的模組都載入完畢之後才執行。
CMD
CMD是SeaJS在推廣過程中對模組定義的規範化產出。它的全稱是Common Module Definition。
CMD也是使用define來定義模組的,CMD推崇一個檔案作為一個模組:
define(id?, deps?, factory)
看起來和AMD的define很類似,都有id,依賴模組和factory。
這裡的factory是一個函式,帶有三個引數,function(require, exports, module)
我們可以在factory中通過require來載入需要使用的模組,通過exports來匯出對外暴露的模組,module表示的是當前模組。
我們看一個例子:
// 定義模組 myModule.js
define(function(require, exports, module) {
var $ = require('jquery.js')
$('div').addClass('active');
});
// 載入模組
seajs.use(['myModule.js'], function(my){
});
所以總結下AMD和CMD的區別就是,AMD前置要載入的依賴模組,在定義模組的時候就要宣告其依賴的模組。
而CMD載入完某個依賴模組後並不執行,只是下載而已,只有在用到的時候才使用require進行執行。
ES modules和現代瀏覽器
ES6和現代瀏覽器對模組化的支援是通過import和export來實現的。
首先看下import和export在瀏覽器中支援的情況:
首先我們看下怎麼使用export匯出要暴露的變數或者方法:
export const name = 'square';
export function draw(ctx, length, x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, length, length);
return {
length: length,
x: x,
y: y,
color: color
};
}
基本上,我們可以使用export匯出var, let, const變數或者function甚至class。前提是這些變數或者函式處於top-level。
更簡單的辦法就是將所有要export的放在一行表示:
export { name, draw, reportArea, reportPerimeter };
export實際上有兩種方式,named和default。上面的例子中的export是named格式,因為都有自己的名字。
下面看下怎麼使用export匯出預設的值:
// export feature declared earlier as default
export { myFunction as default };
// export individual features as default
export default function () { ... }
export default class { .. }
named可以匯出多個物件,而default只可以匯出一個物件。
匯出之後,我們就可以使用import來匯入了:
import { name, draw, reportArea, reportPerimeter } from './modules/square.js';
如果匯出的時候選擇的是default,那麼我們在import的時候可以使用任何名字:
// file test.js
let k; export default k = 12;
// some other file
import m from './test'; // 因為匯出的是default,所以這裡我們可以使用import m來引入
console.log(m); // will log 12
我們可以在一個module中使用import和export從不同的模組中匯入,然後在同一個模組中匯出,這樣第三方程式只需要匯入這一個模組即可。
export { default as function1,
function2 } from 'bar.js';
上面的export from 等價於:
import { default as function1,
function2 } from 'bar.js';
export { function1, function2 };
上面的例子中,我們需要分別import function1 function2才能夠使用,實際上,我們可以使用下面的方式將所有的import作為Module物件的屬性:
import * as Module from './modules/module.js';
Module.function1()
Module.function2()
然後function1,function2就變成了Module的屬性,直接使用即可。
在HTML中使用module和要注意的問題
怎麼在HTML中引入module呢?我們有兩種方式,第一種是使用src選項:
<script type="module" src="main.js"></script>
第二種直接把module的內容放到script標籤中。
<script type="module">
/* JavaScript module code here */
</script>
注意,兩種script標籤的型別都是module。
在使用script來載入module的時候,預設就是defer的,所以不需要顯示加上defer屬性。
如果你在測試的時候使用file:// 來載入本地檔案的話,因為JS模組安全性的要求,很有可能得到一個CORS錯誤。
最後,import() 還可以作為函式使用,來動態載入模組:
squareBtn.addEventListener('click', () => {
import('./modules/square.js').then((Module) => {
let square1 = new Module.Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
square1.draw();
square1.reportArea();
square1.reportPerimeter();
})
});
本文作者:flydean程式那些事
本文連結:http://www.flydean.com/js-modules/
本文來源:flydean的部落格
歡迎關注我的公眾號:「程式那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!