簡介
在很久以前,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的部落格
歡迎關注我的公衆号:「程式那些事」最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!