初學者指南之JavaScript模塊化

初學者指南之JavaScript模塊化

如果你是一個JavaScript的初學者,你很快會被一些行話淹沒,如:“module bundlers vs. module loaders”,“Webpack vs. Browseriy” 和“AMD vs. CommonJS”。

這些JavaScript模塊化系統挺嚇人的,但是理解它們又是對每個web開發者來說至關重要的。

在這片文章裡,前端小號將用簡單的文字加上一點點代碼來揭開這些流行語。希望你能夠得到一些幫助。

有人可以解釋一下什麼是模塊嗎?

好的作者將他們的書分成章節;好的程序員將他們的程序分成模塊。

像書章一樣,模塊只是文字片段(或代碼)的結合。然而,想要創建良好模塊的標準需要具備很多要素:高度獨立的、具有不同的功能、允許在必要時被替換,刪除或添加,而不會中斷整個系統,所謂的鬆耦合。

為什麼使用模塊?

使用模塊有利於系統的擴展,互相依賴的代碼庫有很多好處。在我看來,最重要的是:

  1. 可維護性:根據定義,模塊是獨立的。一個精心設計的模塊旨在儘可能減少對代碼庫部分的依賴,從而可以獨立的增長和改進。當模塊與其他代碼段分離時,更新單個模塊會更容易。
    回到剛才我們講到的書的例子,如果你想更新你書中的章節,那麼如果對其中一章的一個小小的改變需要你調整每一個章節,這將是一場噩夢。相反,你希望每章以同樣的方式寫出這些改進,而不會影響到其他的章節。

  2. 命名空間:在JavaScript中,頂級函數作用域之外的變量是全局變量(意味所有地方都可以訪問它們)。因此,沒有模塊化時通常有“全局變量汙染”或叫“命名空間汙染”這一說法,其中有很多完全不相關的代碼共享全局變量。
    在不相關的代碼之間共享全局變量是一個開發中的大忌諱。正如我們稍後再本文中看到的,模塊允許為我們的變量創建一個私有空間來避免命名空間汙染。

  3. 可重用性:我這裡說實話,我以前經常將之前寫過的代碼複製到一個或多個新的項目中。這是非常不好的做法。例如,我們假設您也是將之前的代碼複製到新的項目中,這種做法看起來一切都很好,但是如果你找到一個更好的方法來編寫其中一些代碼,或者要發現並要解決其中的某些Bug,那麼你必須記得回去在曾經使用過的其他項目更新它。
    這顯然是非常浪費時間的。如果有這樣的情況,那麼等待將它改變成一個可以重複使用的模塊,這不是會更容易嗎?

如何創建模塊?

目前有多種方案來創建模塊。我們來看幾個:

模塊化設計模式

模塊化設計模式用於模擬類的概念(因為JavaScript本身不支持類),因此我們可以將共有和私有方法和變量存儲在單個對象中,類似於在其他編程語言中使用類,如Java或Python。這種做法可以使得將我們想公開的方法創建一個面向公共的API,同時將封閉作用域中的變量和方法封裝起來。

有幾種方法來實現模塊化設計模式。在第一個例子中,我將使用一個匿名閉包。通過將所有代碼放在匿名函數中來幫我們實現目的。(PS:在JavaScript中,函數是創建新作用域的唯一方法。)

例子1:匿名閉包

初學者指南之JavaScript模塊化

anonymousClosure

使用這個結構,我們的匿名函數有自己的執行環境或閉包,然後我們立即執行它。這樣做可以從父(全局)命名空間隱藏變量。這種方法的好處在於,你可以在此函數內部使用局部變量,而不會意外覆蓋現有的全局變量,但仍然可以訪問全局變量,如下所示:

初學者指南之JavaScript模塊化

anonymousClosureWithGlobal

請注意,匿名函數週圍的括號是必需的,因為以關鍵字function開頭的語句總是被認為是函數聲明(PS:不能在JavaScript中使用未命名的函數聲明)。因此,周圍的括號會創建一個函數表達式替代。那麼它就可以被執行了。還有一種稱呼---立即執行函數。

例子2:全局導入

像jQuery這樣的庫使用的另一種流行方法---全局導入。它類似於我們剛剛看到的匿名閉包,我們傳入全局變量作為參數:

初學者指南之JavaScript模塊化

globalimport

在這個例子中,globalVariable是全局唯一的變量。這種方法相對於匿名閉包(立即執行函數)的好處是執行之前聲明瞭全局變量,使得別人更容易閱讀代碼。

例子3:接口對象

JavaScript本身沒有接口設計,另一種方法是使用立即執行函數接口對象創建模塊,如下所示:

初學者指南之JavaScript模塊化

objectInterface

如你所見,這種方法可以通過將它們(例如算平均分和掛科數方法)放在return語句中來決定我們想要保留的私有變量和方法(例如myGrades)以及我們想要公開的變量和方法。

例子4:揭開模塊模式

這與上述方法非常相似,之前它確保所有方法和變量都保持私有,直到最後明確暴露:

初學者指南之JavaScript模塊化

RevealingModulePattern

這些方法可能看起來很多,但它只是模塊化設計模式的冰山一角。

CommonJS 和 AMD

上述方法首先都有一個共同點:使用單個全局變量將其代碼包裝在一個函數中,從而使用閉包作用域為自己創建一個私有命名空間。雖然每種方法都有效且都有各自特點,但卻都有缺點。作為開發人員,你需要知道正確的依賴關係順序來加載文件。例如,假設你的項目中使用了backbone框架,因此你可以將backbone的源代碼以<script>腳本標籤的形式引入到文件中。然而,由於backbone對underscore.js庫有很強的依賴性,那麼backbone的標籤就不能放在underscore.js文件之前。
作為一名開發人員,管理依賴關係和引入順序這些事情有時會令人頭疼。
另一個缺點是它們仍然可能導致命名空間的衝突。例如,如果你的兩個模塊具有相同的名稱,該怎麼辦?或者如果你有多個版本的模塊,你需要引入多個嗎?
所以你可能想知道:能否設計出一種方式,不需要遍歷全局作用域就可以去訪問的模塊接口?幸運的是,答案是肯定的。
有兩種受歡迎的方法:CommonJS和AMD。

CommonJS

CommonJS是一個志願工作組,負責設計和實現用於申明模塊的JavaScript API。
CommonJS模塊本質上市一個可重用的JavaScript腳本,可導出特定的對象,使其可以用於其他模塊在其程序中的需求。如果你已經在用nodejs編程,那麼你講非常熟悉此格式。
使用CommonJS,每個JavaScript文件將模塊存儲在其獨特的模塊上下文中(就像將其包裝在一個閉包中)。在這個作用域中,我們使用module.export對象來公開模塊,或者導入它們。
當你定義一個CommonJS模塊時,可能看起來像這樣:

初學者指南之JavaScript模塊化

myModule

我們使用特殊對象模塊,並將我們的函數的引用放在module.exports中。這使得CommonJS模塊系統知道我們要公開的內容,以便其他文件可以使用它。那麼當有人想要使用myModule時,他們可以在他們的文件引入它,就像這樣子:

初學者指南之JavaScript模塊化

require

對於我們之前討論的模塊化設計模式,這種方法有兩個明顯的好處:

  1. 避免全局命名空間汙染

  2. 明確我們的依賴

此外,語法非常緊湊,易讀。另一件需要注意的事情是,CommonJS採用服務器優先方式並同步加載模塊。這很重要,因為如果我們需要另外三個模塊,它們將被逐個加載。
目前,這種方式在服務器上運行良好,但不幸的是,在為瀏覽器寫JavaScript時更難使用。因為從網上下載模塊要比從硬盤上讀取文件速度慢得多。只要加載模塊的腳本正在執行,那麼它將阻塞瀏覽器運行其他任何東西,直到它加載完所有模塊。

AMD

CommonJS一切都很好,但是如果我們要異步加載模塊怎麼辦?答案成為異步模塊定義,簡稱AMD。使用AMD加載模塊看起來像這樣:

初學者指南之JavaScript模塊化

amd

這裡將作依賴模塊名稱的一個數組作為define函數其第一個參數。這些依賴關係在後臺加載(以非阻塞的方式),一旦加載,就定義了調用它給出的回調函數。接下來,回調函數作為參數使用加載的依賴模塊,允許函數使用這些依賴模塊。最後,所依賴的模塊本身也必須使用define關鍵字來定義。例如,myModule如下所示:

初學者指南之JavaScript模塊化

amdDefine

所以這麼看來與CommonJS不同,AMD採用的是瀏覽器優先的方法和異步行為來完成加載工作的。除了異步之外,AMD的另外一個好處是,你的模塊可以是對象,函數,構造函數,字符串,JSON和許多其他類型,而CommonJS僅僅支持對象作為模塊。
話雖如此,AMD和通過CommonJS提供的面向服務器的功能模塊不兼容,而且與簡單的require語句相比,函數封裝的語法更為冗長。

UMD

對於需要支持AMD和CommonJS功能的項目,還有另外一種格式:通用模塊定義,即UMD。
UMD本質上創造了一種使用兩者之一的方法,同時也支持全局變量的定義。因此,UMD模塊能夠在客戶端和服務端同時工作。以下是UMD如何工作的快速瞭解:

初學者指南之JavaScript模塊化

umd

簡單理解就是判斷不同環境,使用不同的格式封裝模塊。

原生JS

“哈嘍,你還在嗎?” 是不是有點暈了,別急。因為我們還有一個類型的模塊定義方法要講述。
你可能已經注意到,上面的模塊都不是JavaScript本身。相反,我們已經創建了使用模塊化設計模式,CommonJS或AMD來模擬模塊系統的方法。
幸運的是,ECMAScript6已經加入了內置的模塊系統。ES6提供了導入和導出模塊的各種可能性。
相對於CommonJS和AMD,ES6模塊的優點是它提供了瀏覽器和nodejs兩個世界所需的最佳功能:簡明和申明式語法和異步加載,以及更好的支持循環依賴性等附加好處。以下是一個ES6模塊系統的例子:

初學者指南之JavaScript模塊化

es6

在這個例子中,我們基本上創建了兩個模塊的對象:一個用於導出它,一個在我們需要的時候引入。
此外,在main.js中的對象目前是與原始模塊斷開的狀態,這就是為什麼即使我們執行increment方法,它仍然返回1。所以執行increment方法只會影響原始模塊,不會改變你引入的對象。需要改變你引入的對象唯一的方式是手動執行增加:

初學者指南之JavaScript模塊化

es6-2

另一方面,ES6創建了另外一種使用import關鍵字的簡單明瞭寫法:

初學者指南之JavaScript模塊化

es6-3

很cool,對嗎?是不是有點類似於java或python了。這種方式允許將模塊拆分成多個較小的部分導出。然後你可以引入過來再次合併。

講了這麼多,真希望大家都能從這篇文章裡更好的瞭解JavaScript中的模塊。
如果你覺得這篇文章對你有幫助,請關注我們前端小號頭條號!~謝謝

相關推薦

推薦中...