我們都知道 JavaScript 中並沒有模組的概念,一開始 JavaScript 的出現只是作為簡單指令碼語言來實現簡單的頁面邏輯,而隨著網際網路的發展和 web 2.0 時代的到來,前端程式碼呈現井噴式發展,隨著程式碼量的增加,模組缺失的問題日益凸顯,而同時 JavaScript 社群也做了很多探索。
那麼什麼是模組呢?
模組,是指能夠單獨命名並獨立地完成一定功能的程式語句的集合(即程式程式碼和資料結構的集合體)。
所以,模組的核心就是需要完成特定的功能,並且其很重要的一點就是需要解決引用依賴以及被依賴的問題。
函式
從定義上來說,其實函式也可以被當作是一個模組。
在 myModule.js 中新增如下函式
function add(x, y) {
return x + y;
}
function minus(x, y) {
return x - y;
}
複製程式碼
通過 script 標籤引入 myModule.js 後可直接使用裡面的函式
<script src="myModule.js"></script>
<script>
console.log(add(1, 2)) // 3
console.log(minus(2, 1)) // 1
</script>
複製程式碼
在 myModule.js 中,每一個函式都可以被認為是一個模組。通過函式方式定義模組主要有兩個缺陷:一是會汙染全域性變數,無法保證各模組間的變數名不衝突;二是需要手動維護依賴的順序,如果 myModule.js 中的模組需要依賴其它模組(如 jQuery),則該模組(jQuery)需要在 myModule.js 之前引用。
CommonJS
自 2009 年 NodeJS 誕生後,JavaScript 模組化程式設計正式進入人們的視野,而 NodeJS 的模組化系統,便是參照 CommonJS 規範實現的。
在 CommonJS 規範中,一個檔案就是一個模組,每個模組都有各自的作用域,即在一個模組中的變數、函式是私有的,外部無法訪問。
在模組內部,module 變數物件代表當前模組,其 exports 屬性也是一個物件,代表對外的介面,所以我們將需要對外暴露的內容放進 module.exprots 物件即可;而引用模組則使用 require 函式。
使用 CommonJS 規範來定義 myModule 模組,在 myModule.js 中寫入如下程式碼
function add(x, y) {
return x + y;
}
function minus(x, y) {
return x - y;
}
module.exports = {
add: add,
minus: minus
}
複製程式碼
在 main.js 中引用 myModule 模組
var myModule = require('./myModule.js');
console.log(myModule.add(1,2)); // 3
console.log(myModule.minus(2,1)); // 1
複製程式碼
但是,由於在 CommonJS 規範中,require 載入模組的方式是同步載入,使得其不適合在瀏覽器端使用。在伺服器端同步載入模組,等待時間取決於硬碟的讀取時間,而在瀏覽器端同步載入模組,等待時間取決於網速快慢,這使得在等待載入模組的過程中,瀏覽器會處於假死的狀態。
AMD(Asynchronous Module Definition)
AMD 即非同步模組定義,是 RequireJS 在推廣過程中對模組定義的規範化產出。同樣的,其規定一個檔案就是一個模組,檔名即模組名。
AMD 使用 define 函式定義模組;使用 require 函式引用模組。
define 函式使用方式如下
define(id?, dependencies?, factory);
複製程式碼
定義 myModule.js 模組
define(['depenModule'], function (depenModule) {
//do something
});
複製程式碼
require 接收兩個引數,第一個引數為所依賴的模組標識陣列;第二個引數為依賴模組載入完成後的回撥函式。
在 main.js 中引用 myModule 模組
require(['myModule'], function (myModule){
//do something
});
複製程式碼
AMD 推崇依賴前置,需要先非同步載入其所需的依賴模組後才會執行相應回撥函式中的程式碼。
CMD(Common Module Definition)
CMD 即公共模組定義,是 SeaJS 在推廣過程中對模組定義的規範化產出。同樣的,其規定一個檔案就是一個模組,檔名即模組名。
其使用 define 函式定義模組;使用 require 函式引用模組。
define 函式使用方式如下
define(factory)
複製程式碼
定義 myModule.js 模組
define(function(require, exports, module) {
//do something
});
複製程式碼
在 main.js 中引用 myModule 模組
var myModule = require('./myModule.js');
//do something
複製程式碼
CMD 推崇依賴就近,在書寫程式碼的過程中再根據其所需要的依賴 require 進來。
AMD 與 CMD 對於依賴的模組都是非同步載入。 其最大的區別是對依賴模組的執行時機處理不同。
AMD 依賴前置,瀏覽器會立即載入其依賴模組;而 CMD 是依賴就近,需要將模組轉為字串解析才能確認其依賴模組並載入,這是一種犧牲效能來帶來開發的便利性的做法。
UMD(Universal Module Definition)
UMD 即通用模組定義,是一種基本上可以在任何一個模組環境中工作的規範。
一段典型的 UMD 程式碼如下所示
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : // CMD
typeof define === 'function' && define.amd ? define(['exports'], factory) : // AMD
(factory((global['module'] = {}))); // Browser globals
}(this, (function (exports) {
'use strict';
exports.x = x;
Object.defineProperty(exports, '__esModule', { value: true });
})));
複製程式碼
其原理是通過對不同的環境的判斷做相應的處理。
ES6 模組
在 ES6 中,JavaScript 終於有了自己真正模組的概念。ES6 在語言標準的層面上,實現了模組功能,而且實現得相當簡單,完全可以取代之前的規範,成為瀏覽器和伺服器通用的模組解決方案。
在 ES6 模組系統中,通過 export 和 export default 命令規定模組的對外介面,import 命令輸入模組提供的功能。
myModule.js 中
function add(x, y) {
return x + y;
}
function minus(x, y) {
return x - y;
}
export {
add,
minus
}
複製程式碼
main.js 中
import { add, minus } from './myModules.js';
console.log(add(1, 2)) // 3
console.log(minus(2, 1)) // 1
複製程式碼
對於 ES6 的模組在此不多做介紹,參考文件見阮一峰大神的《 ECMAScript 6 入門 》。
寫在最後
在 JavaScript 模組化的探索道路上,出現了很多優秀的規範與框架,除了上述具有代表性的規範外,還有像 YUI、KMD 等其它優秀的規範框架。
雖然說 JavaScript 的模組化發展史(更確切的應該是 JavaScript 發展史)一路充滿艱辛坎坷,但其實我們可以看到它正走在正確的道路上,越來越好,衷心希望未來 JavaScript 能夠越來越強大,能夠讓我們在未來遇見更多的可能。
參考資料
公眾號不定時分享個人在前端方面的學習經驗,歡迎關注。