首頁 > 軟體

伺服器端JavaScript運行環境Node.js的依賴性管理

2021-05-26 12:00:56

眾所周知,Node.js是一個基於Chrome V8引擎的伺服器端JavaScript運行環境。它採用了一種事件驅動的、非阻塞式的I/O模式,運行起來既輕量級又高效。誠然,我們可以使用單個js檔案,來編寫出應用程式所涉及到的全部內容,但這樣既不靈活,又不夠模組化。而Node.js的出現,讓模組化程式碼的編寫變得非常簡便。因此,對於Node.js的核心,我們需要理解和掌握的一個重要概念便是:依賴關係的管理。本文將和您一起探討依賴項管理的各種模式,以及Nodejs是如何載入依賴項的。

在深入探討細節之前,讓我們首先弄清楚什麼是模組。簡而言之,模組是一段程式碼。為了共享和重用,我們需要將程式碼進行分組放置。通過模組,我們可以將複雜的應用程式分解到小塊程式碼中。同時,模組也能夠幫助我們理解程式程式碼的意圖,並發現或修復各種錯誤。

自2009年以來,CommonJS便實現了Javascript的模組化規範。它規範了模組的特性和各個模組之間的相互依賴性。由於每個檔案都被當做一個模組(通常,module變數代表了當前模組),而且有自己的作用域,因此每個檔案裡面的變數、函數、以及類,都是私有的,且對於其他模組是不可見的。而模組的exports屬性便是對外的介面。只有通過exports匯出的屬性,才能被其他模組識別和載入。而Node是基於CommonJs規範來實現模組的同步與載入。也就是說,我們可以通過在模組中呼叫require()方法,來接收模組的標識,並根據node的模組引入規則,引入其他模組,進而呼叫對應的屬性和方法。

在此,我假設您已經掌握了Nodejs的上述基礎知識。當然,如果您是一名Node.js的新手,則可以通過檢視Node.js的相關簡介來了解更多背景資訊。

設定應用

讓我們從最簡單的開始。假設我已經為某個項目創建了一個目錄。通過運用npm init命令對其初始化後,我們將創建app.js和appMsg.js,兩個JavaScript檔案。下圖展示了本項目的目錄結構,我們將其作為管理的起點。如果您感興趣的話,可以從文末給出的git儲存庫連結中,下載該項目的最終原始碼。

預設情況下,這兩個.js檔案均為空。讓我們通過如下更改,來更新appMsgs.js檔案。

上面的程式碼段展示了module.exports關鍵字的用法。此語法用於公開給定檔案(此處為appMsgs.js)中的屬性或物件,以便能夠在另一個檔案(如本例中的app.js)中被直接使用到。

在該系統中,每個檔案都可以訪問到名為module.exports的檔案。因此,我們在appMsgs.js檔案中公開了一些項目,以方便觀察app.js是如何使用(require)某些屬性的。

顯然,require關鍵字可以方便我們引用某個檔案。也就是說,當我們執行require時,它將返回一個代表著模組化程式碼段的物件。因此,我們可以將其分配給一個appMsgs變數,然後在console.log的語句中簡單地使用該屬性。當代碼被執行時,我們將看到如下輸出:

該require通過執行JavaScript,構造出一個具有某種功能函數的物件,作為返回。它們既可能是一個類建構函式,又可以是其中包含了許多元素、或一些簡單屬性的物件。針對不同的模式,我們既可以匯出多個物件,又可以只匯出那些複雜的物件。可見,通過require和module.exports,我們可以創建出模組化的應用程式。

值得注意的是,應用程式所需的功能函數只會僅載入程式碼一次。也就是說,無論執行了什麼程式碼,它們都不會被執行第二次。那麼,如果別的程式也要通過require來獲取物件的話,它將只能獲得該物件的快取版本。

下面,讓我們來看看匯出的方式。

如上面程式碼段所示,我對前面的程式碼進行了更改。現在,我不再公佈物件了,而是匯出了一個功能函數(function)。該函數在每次被呼叫時,都需要執行該程式碼。

下面,讓我們來看看如何在app.js檔案中使用它:

更新app.js檔案

除了呼叫某個屬性,我們還可以像執行函數一樣去執行它。因此,這裡的區別主要是,每當我們執行該程式碼時,函數內部的程式碼都會被重新執行(re-executed)。

下面是我們重新運行該程式碼段的輸出:

至此,我們已經看到了module.exports的兩種模式,及其兩者的區別。還有一個常見的模式是,將其用作構造器方法(constructor method)。下面,讓我們再來看一個例子:

下面是更改過的app.js檔案:

從本質上講,這與您在JavaScript中創建偽類(pseudo-class),並且創建它的各種例項(instances)是一致的。

下面是更改後的輸出:

接著,讓我們接著討論此類模式的另一個示例。如下程式碼段所示,我創建了一個名為userRepo.js的新檔案。

下面是更改後的app.js檔案。

下圖是該更改被執行後的結果:

當然,針對單個檔案都去使用require的情況並不常見。接下來,讓我們再討論另一種模式--資料夾的依賴性。

資料夾依賴性

為了弄清Node.js是如何查詢依賴性的,讓我們重溫一下前面例子中的JavaScript程式碼:

var appMsgs = require(「 ./appMsgs」)

Node不但會查詢appMsgs.js檔案,而且會查詢作為目錄的appMsgs,並取出它的值。

我創建了一個名為logger的資料夾,並在其中創建了一個index.js檔案,其內容如下面的程式碼段所示:

下面是require此模組的app.js檔案:

可見,在本例中,我們可以寫出這樣的JavaScript程式碼:

var logger = require(「./logger/index.js」)

上述較長的路徑形式肯定是正確的。但是,我們其實只需寫出如下的JavaScript程式碼即可:

var logger = require(「./logger」)

由於沒有logger.js,而只有logger目錄,因此在預設情況下,Node將載入index.js作為logger的起點。我們可以通過如下命令,來驗證其輸出結果:

在此,您可能心生疑慮:我們為什麼如此費盡周折地創建資料夾和index.js呢?其背後的原因在於:您可能會將一些複雜的依賴項放在一起,而這些依賴項也可能還有其他的依賴項。而對於需要logger的呼叫者(caller)而言,它們不需要知道其他依賴項的存在。

這便是一種封裝形式(encapsulation)。我們完全可以在多個檔案中,構建更為複雜的程式碼段;而在使用者(consumer)角度,它們只需使用一個檔案足矣。可見,資料夾是管理此類依賴性關係的更好方法。

Node程式包管理器(NPM)

第三類值得我們探討的依賴性管理是NPM。顧名思義,NPM是Node.js程式包的管理和分發工具,它相當於後端的Maven。它可以讓Javascript開發者更加輕鬆的共享和共用程式碼段。

通常,我們可以使用如下npm命令,來安裝依賴項:

npm install underscore;

如下程式碼段所示,我們也可以簡單地在app.js中require它:

如您所見,我們可以通過underscore的軟體包來使用各項功能。同理,當需要用到此類模組時,我們並沒有指定檔案的路徑,而只需使用其名稱即可。Node.js將會從您的應用程式的node_modules資料夾中,自動載入到其對應的模組。

下面是程式碼執行後的輸出結果:

小結

綜上所述,我們討論了Node.js是如何管理其依賴性關係的。您可以從Git儲存庫下載上述示例的原始碼。


IT145.com E-mail:sddin#qq.com