前言
本文可能不涉及一些基礎概念,比如什麼是模組,對每種方案也沒有都做詳細的解釋(如果解釋的話,每個都值得一篇文章),準備和落實這篇文章主要是有兩方面原因。
- 平時遇到的一些名詞有些摸不著頭腦,總是大概知道是個什麼意思,但細想又不知道其原理和聯絡,比如平時遇到
CommonJS
,seaJS
,CMD
,AMD
,UMD
,Browserify
,RequireJS
,知道他們和js模組有關,但具體是什麼關係,js模組化應該用哪個的卻是不知道。 - 使用webpack做模組打包,不知道過程中做了什麼(除了plugins和loader),是從什麼樣的模組打包成什麼樣的模組,最後又變成了什麼。
綜上兩點,就是對JS的模組不熟悉導致的。
本文目標
對JS模組化有一個直面的瞭解,並能捋清楚各個規範之間的之間的聯絡,再遇到這些名詞不會再有它認識我,我不認識它
的感覺
演變過程
首先來說為什麼需要模組化,這個大家心裡應該都有一兩個答案,比如避免命名衝突啊,全域性變數過多啊,依賴不好管理啊等等等等。總得來說模組化大方向上有幾個好處。
- 易於維護
- 名稱空間(解決命名衝突)
- 複用性
- ...
總體上模組化基本上是一個百利無一害的實現
原始時代
開始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
既然服務端能實現了這種模組化的規範,瀏覽器上對此也是非常迫切的,自然也是想要實現這塊,但是直接拿來用是有一些問題,
- node中require是同步載入的,因為直接從記憶體或硬碟裡讀就可以了,而在瀏覽器上不能同步載入,因為瀏覽器上每一個檔案都是需要下載下來的,都是需要時間的,而且瀏覽器上下載js都是通過script來載入的,不能同步執行,所以也就沒辦法同步載入模組了。
- 沒有立即執行函式的包裹,載入的模組變數又暴露在全域性上了。
因此如果想在瀏覽器上使用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模組化有一個較為全面的瞭解,面對這些名詞不再預設,並瞭解他們之間的關係。
文章倉促,有不足之處希望多多指出。
如果你看完對你有些許的幫助,那是意外之喜?。