JavaScript模組化演化史

wingtao發表於2019-04-10

前言

本文可能不涉及一些基礎概念,比如什麼是模組,對每種方案也沒有都做詳細的解釋(如果解釋的話,每個都值得一篇文章),準備和落實這篇文章主要是有兩方面原因。

  1. 平時遇到的一些名詞有些摸不著頭腦,總是大概知道是個什麼意思,但細想又不知道其原理和聯絡,比如平時遇到CommonJS,seaJS,CMD,AMD,UMD,Browserify,RequireJS,知道他們和js模組有關,但具體是什麼關係,js模組化應該用哪個的卻是不知道。
  2. 使用webpack做模組打包,不知道過程中做了什麼(除了plugins和loader),是從什麼樣的模組打包成什麼樣的模組,最後又變成了什麼。

綜上兩點,就是對JS的模組不熟悉導致的。

本文目標

對JS模組化有一個直面的瞭解,並能捋清楚各個規範之間的之間的聯絡,再遇到這些名詞不會再有它認識我,我不認識它的感覺

演變過程

首先來說為什麼需要模組化,這個大家心裡應該都有一兩個答案,比如避免命名衝突啊,全域性變數過多啊,依賴不好管理啊等等等等。總得來說模組化大方向上有幾個好處。

  1. 易於維護
  2. 名稱空間(解決命名衝突)
  3. 複用性
  4. ...

總體上模組化基本上是一個百利無一害的實現

原始時代

開始JS沒有模組的概念,因此導致了很多問題,比如命名衝突,比如:

var origin = 100;
....其他程式碼
var origin = 1232;
....其他程式碼
console.log(origin)//目標是讓它輸出100,但實際會輸出1232,因為後來的命名覆蓋
複製程式碼

還有依賴問題,比如a.js依賴了b.js,那麼下載時必須是先下載b,在下載a,順序一定不能錯。

IIFE

為了解決這些沒有模組帶來的問題,前輩們提出了使用IIFE來模仿模組,如下:

(function(){
	var name = 'wingtao;
	var sayHello = function (){
		console.log('hello '+name);
	}
	sayHello(); // hello wingtao
})()
複製程式碼

上面利用立即執行函式模擬了一個模組,該模組中的變數外界無法訪問,避免了命名衝突的問題,jQuery就用了這種方式,

(function(global){
	global.jquery = ...
})(window);
複製程式碼

但還是沒有完全實現模組化,如對模組依賴的管理、如何將api暴露出來而不汙染全域性環境

CommonJS && nodejs

CommonJS社群首先提出了模組化的規範CommonJS,所以CommonJS是一個規範!在node上只需要簡單的require和exports就可以實現模組的匯入和匯出,如下:

a.js
exports.add = function(a,b){
	return a+b;
}

b.js 
var add = require('a.js').add;
console.log(add(1,2))//3
複製程式碼

看起來非常棒!而且nodeJS模組實現了這種規範,意味著在node中可以直接使用這種方式。

CommonJS && Browserify

既然服務端能實現了這種模組化的規範,瀏覽器上對此也是非常迫切的,自然也是想要實現這塊,但是直接拿來用是有一些問題,

  1. node中require是同步載入的,因為直接從記憶體或硬碟裡讀就可以了,而在瀏覽器上不能同步載入,因為瀏覽器上每一個檔案都是需要下載下來的,都是需要時間的,而且瀏覽器上下載js都是通過script來載入的,不能同步執行,所以也就沒辦法同步載入模組了。
  2. 沒有立即執行函式的包裹,載入的模組變數又暴露在全域性上了。

因此如果想在瀏覽器上使用CommonJS是需要改造的,對此人們分成了幾派,一派是認為還是按照CommonJS規範來,只是加上函式包裹和非同步載入,在瀏覽器上能執行就行了;一派認為CommonJS不適合瀏覽器端,需要一個新的規範;第三方是個“和稀泥”的,認為CommonJS和重新改革都有可取之處,所以各取所長。

其中第一派堅持使用CommonJS的做出了瀏覽器端的實現Browserify,名字也很形象,Browserify可以將node端模組檔案轉換為瀏覽器可識別的模組檔案。所以Browserify是CommonJS在瀏覽器端的實現

AMD && RequireJS

AMD(規範)其實就是上面說的第二派,就是拋棄CommonJS,提出新的可非同步載入的模組規範。AMD最大的特點便是可以非同步載入模組,它的實現是RequireJS,編寫時像這樣:

define(['myModule', 'myOtherModule'],function(myModule, myOtherModule) {
	console.log(myModule.hello());
});
複製程式碼

過程是先載入依賴myModule,myOtherModule(後臺不阻塞的方式載入),載入完成後執行回撥函式,其中回撥函式的引數便是已經載入完成的模組。其實AMD還是有很多問題的,比如define的時候所有依賴要挨個寫一遍,比如不管現在用不用的到都會把依賴先下載下來,不過這些問題AMD都有優化,這裡不提。

UMD

UMD全稱是Universal Module Definition,目的相容CommonJS和AMD,所以它會做一層判斷,判斷當前環境是瀏覽器還是node,如果是瀏覽器則使用AMD,node環境使用CommonJS方式,UMD實現了兩種環境的相容,但同時也導致了十分臃腫,肉眼觀察實在有點費勁。

CMD && seaJS

提起CMD,不怕被笑話,我之前還以為和CommonJS是同一個東東呢?。seaJS是阿里前端工程師玉伯做出來的,並提出了CMD,CMD吸取了AMD和CommonJS兩者的優點,融合了百家之長(但好像只在國內有影響,國外影響有限),所以CMD是規範而seajs是它的實現。

ES6 Module

以上說的種種方式都是因為ECMA缺乏官方的模組規範才出來的,既然對模組化的需求這麼旺盛,官方在ES2015(ES6)裡也就提出了官方的模組化方案,主要使用import和export,用法非常簡單,而且它和之前的方案的區別除了它是官方提出並且寫法簡單之外,還有重要的一點就是它是靜態解析的,什麼是靜態解析呢?另開一篇文章再講,不過這個特性可以帶來很多優化,如tree-shaking。ES6模組機制理論上是瀏覽器原生支援的,但實際上現在支援度還不夠,這個大家應該也能理解,不過在未來應該可能就能在瀏覽器中直接載入匯出模組了,

webpack && gulp && rollup

這些工具其實和上面講的模組規範已經不是一個維度的事情了,上面說的是模組化,這些是一些可以打包的工具,打包什麼呢?模組!這就是他們之間的聯絡了。就不展開講了,這是三個東西太多了。

總結

簡單總結一下上面講的東西,其實就是講了下js模組的幾個方案,每個方案都有自己的規範,然而只有規範還不行,需要有實現來支援它,所以總得來說就是:

環境 規範 實現
node CommonJS nodejs 模組
瀏覽器 CommonJS Browserify
瀏覽器 AMD RequireJS
瀏覽器 UMD ?兩者
瀏覽器 CMD SeaJS

寫下來這些就是希望自己能對js模組化有一個較為全面的瞭解,面對這些名詞不再預設,並瞭解他們之間的關係。

文章倉促,有不足之處希望多多指出。

如果你看完對你有些許的幫助,那是意外之喜?。

參考文章

JavaScript模組:指南

JavaScript模組:模組打包

相關文章