More  

收藏本站

電腦請使用 Ctrl + D 加入最愛
手機請使用 收藏
關閉

小編的世界 優質文選 主機

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


字體大小:
2021年5月27日 -
:     
 

51CTO

北京無憂創想信息技術有限公司

眾所周知,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存儲庫下載上述示例的源代碼。