我們都知道 JavaScript 中并沒有模塊的概念,一開始 JavaScript 的出現(xiàn)只是作為簡單腳本語言來實現(xiàn)簡單的頁面邏輯,而隨著互聯(lián)網(wǎng)的發(fā)展和 web 2.0 時代的到來,前端代碼呈現(xiàn)井噴式發(fā)展,隨著代碼量的增加,模塊缺失的問題日益凸顯,而同時 JavaScript 社區(qū)也做了很多探索。
那么什么是模塊呢?
模塊,是指能夠單獨命名并獨立地完成一定功能的程序語句的集合(即程序代碼和數(shù)據(jù)結(jié)構(gòu)的集合體)。
所以,模塊的核心就是需要完成特定的功能,并且其很重要的一點就是需要解決引用依賴以及被依賴的問題。
函數(shù)
從定義上來說,其實函數(shù)也可以被當(dāng)作是一個模塊。
在 myModule.js 中新增如下函數(shù)
function add(x, y) {
return x + y;
}
function minus(x, y) {
return x - y;
}
通過 script 標(biāo)簽引入 myModule.js 后可直接使用里面的函數(shù)
<script src="myModule.js"></script>
<script>
console.log(add(1, 2)) // 3
console.log(minus(2, 1)) // 1
</script>
在 myModule.js 中,每一個函數(shù)都可以被認(rèn)為是一個模塊。通過函數(shù)方式定義模塊主要有兩個缺陷:一是會污染全局變量,無法保證各模塊間的變量名不沖突;二是需要手動維護(hù)依賴的順序,如果 myModule.js 中的模塊需要依賴其它模塊(如 jQuery),則該模塊(jQuery)需要在 myModule.js 之前引用。
CommonJS
自 2009 年 NodeJS 誕生后,JavaScript 模塊化編程正式進(jìn)入人們的視野,而 NodeJS 的模塊化系統(tǒng),便是參照 CommonJS 規(guī)范實現(xiàn)的。
在 CommonJS 規(guī)范中,一個文件就是一個模塊,每個模塊都有各自的作用域,即在一個模塊中的變量、函數(shù)是私有的,外部無法訪問。
在模塊內(nèi)部,module 變量對象代表當(dāng)前模塊,其 exports 屬性也是一個對象,代表對外的接口,所以我們將需要對外暴露的內(nèi)容放進(jìn) module.exprots 對象即可;而引用模塊則使用 require 函數(shù)。
使用 CommonJS 規(guī)范來定義 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 規(guī)范中,require 加載模塊的方式是同步加載,使得其不適合在瀏覽器端使用。在服務(wù)器端同步加載模塊,等待時間取決于硬盤的讀取時間,而在瀏覽器端同步加載模塊,等待時間取決于網(wǎng)速快慢,這使得在等待加載模塊的過程中,瀏覽器會處于假死的狀態(tài)。
AMD(Asynchronous Module Definition)
AMD 即異步模塊定義,是 RequireJS 在推廣過程中對模塊定義的規(guī)范化產(chǎn)出。同樣的,其規(guī)定一個文件就是一個模塊,文件名即模塊名。
AMD 使用 define 函數(shù)定義模塊;使用 require 函數(shù)引用模塊。
define 函數(shù)使用方式如下
define(id?, dependencies?, factory);
定義 myModule.js 模塊
define(['depenModule'], function (depenModule) {
//do something
});
require 接收兩個參數(shù),第一個參數(shù)為所依賴的模塊標(biāo)識數(shù)組;第二個參數(shù)為依賴模塊加載完成后的回調(diào)函數(shù)。
在 main.js 中引用 myModule 模塊
require(['myModule'], function (myModule){
//do something
});
AMD 推崇依賴前置,需要先異步加載其所需的依賴模塊后才會執(zhí)行相應(yīng)回調(diào)函數(shù)中的代碼。
CMD(Common Module Definition)
CMD 即公共模塊定義,是 SeaJS 在推廣過程中對模塊定義的規(guī)范化產(chǎn)出。同樣的,其規(guī)定一個文件就是一個模塊,文件名即模塊名。
其使用 define 函數(shù)定義模塊;使用 require 函數(shù)引用模塊。
define 函數(shù)使用方式如下
define(factory)
定義 myModule.js 模塊
define(function(require, exports, module) {
//do something
});
在 main.js 中引用 myModule 模塊
var myModule = require('./myModule.js');
//do something
CMD 推崇依賴就近,在書寫代碼的過程中再根據(jù)其所需要的依賴 require 進(jìn)來。
AMD 與 CMD 對于依賴的模塊都是異步加載。 其最大的區(qū)別是對依賴模塊的執(zhí)行時機處理不同。
AMD 依賴前置,瀏覽器會立即加載其依賴模塊;而 CMD 是依賴就近,需要將模塊轉(zhuǎn)為字符串解析才能確認(rèn)其依賴模塊并加載,這是一種犧牲性能來帶來開發(fā)的便利性的做法。
UMD(Universal Module Definition)
UMD 即通用模塊定義,是一種基本上可以在任何一個模塊環(huán)境中工作的規(guī)范。
一段典型的 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 });
})));
其原理是通過對不同的環(huán)境的判斷做相應(yīng)的處理。
ES6 模塊
在 ES6 中,JavaScript 終于有了自己真正模塊的概念。ES6 在語言標(biāo)準(zhǔn)的層面上,實現(xiàn)了模塊功能,而且實現(xiàn)得相當(dāng)簡單,完全可以取代之前的規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案。
在 ES6 模塊系統(tǒng)中,通過 export 和 export default 命令規(guī)定模塊的對外接口,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 模塊化的探索道路上,出現(xiàn)了很多優(yōu)秀的規(guī)范與框架,除了上述具有代表性的規(guī)范外,還有像 YUI、KMD 等其它優(yōu)秀的規(guī)范框架。
雖然說 JavaScript 的模塊化發(fā)展史(更確切的應(yīng)該是 JavaScript 發(fā)展史)一路充滿艱辛坎坷,但其實我們可以看到它正走在正確的道路上,越來越好,衷心希望未來 JavaScript 能夠越來越強大,能夠讓我們在未來遇見更多的可能。