溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Node中的文件模塊和核心模塊是什么

發(fā)布時(shí)間:2022-07-19 09:35:35 來(lái)源:億速云 閱讀:108 作者:iii 欄目:web開(kāi)發(fā)

本篇內(nèi)容介紹了“Node中的文件模塊和核心模塊是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

Node中的文件模塊和核心模塊是什么

文件模塊

什么是文件模塊呢?

在 Node 中,使用 .、.. 或 / 開(kāi)頭的模塊標(biāo)識(shí)符(也就是使用相對(duì)路徑或絕對(duì)路徑)來(lái) require 的模塊,都會(huì)被當(dāng)作文件模塊。另外,還有一類特殊的模塊,雖然不含有相對(duì)路徑或絕對(duì)路徑,也不是核心模塊,但是會(huì)指向一個(gè)包,Node 在定位這類模塊時(shí),會(huì)用 模塊路徑 逐個(gè)查找該模塊,這類模塊被稱為自定義模塊。

因此,文件模塊包含兩類,一類是帶路徑的普通文件模塊,一類是不帶路徑的自定義模塊。

文件模塊在運(yùn)行時(shí)動(dòng)態(tài)加載,需要完整的文件定位、編譯執(zhí)行過(guò)程,速度比核心模塊慢。

對(duì)于文件定位而言,Node 對(duì)這兩類文件模塊的處理有所不同。我們來(lái)具體看看這兩類文件模塊的查找流程。

普通文件模塊的查找

對(duì)于普通的文件模塊,由于攜帶路徑,指向非常明確,查找耗時(shí)不會(huì)很久,因此查找效率比下文要介紹的自定義模塊要高一些。不過(guò)還是有兩點(diǎn)需要注意。

一是通常情況下,使用 require 引入文件模塊時(shí)一般都不會(huì)指定文件擴(kuò)展名,比如:

const math = require("math");

由于沒(méi)有指定擴(kuò)展名,Node 還不能確定最終的文件。在這種情況下,Node 會(huì)按 .js、.json、.node 的順序補(bǔ)足擴(kuò)展名,依次嘗試,這個(gè)過(guò)程被稱為 文件擴(kuò)展名分析。

另外需要注意的是,在實(shí)際開(kāi)發(fā)中,除了 require 一個(gè)具體的文件外,我們通常還會(huì)指定一個(gè)目錄,比如:

const axios = require("../network");

在這種情況下,Node 會(huì)先進(jìn)行文件擴(kuò)展名分析,如果沒(méi)有查找到對(duì)應(yīng)文件,但是得到了一個(gè)目錄,此時(shí) Node 會(huì)將該目錄當(dāng)作一個(gè)包來(lái)處理。

具體而言,Node 會(huì)將目錄中的 package.jsonmain 字段所指向的文件作為查找結(jié)果返回。如果 main 所指向的文件錯(cuò)誤,或者壓根不存在 package.json 文件,Node 會(huì)使用 index 作為默認(rèn)文件名,然后依次使用 .js.node 進(jìn)行擴(kuò)展名分析,逐個(gè)查找目標(biāo)文件,如果沒(méi)有找到的話就會(huì)拋出錯(cuò)誤。

(當(dāng)然,由于 Node 存在兩類模塊系統(tǒng) CJS 和 ESM,除了查找 main 字段外,Node 還會(huì)采用其他方式,由于不在本文討論范圍內(nèi),就不再贅述了。)

自定義模塊的查找

剛才提到,Node 在查找自定義模塊的過(guò)程中,會(huì)使用到模塊路徑,那什么是模塊路徑呢?

熟悉模塊解析的朋友應(yīng)該都知道,模塊路徑是一個(gè)由路徑組成的數(shù)組,具體的值可以看以下這個(gè)示例:

// example.js
console.log(module.paths);

打印結(jié)果:

Node中的文件模塊和核心模塊是什么

可以看到,Node 中的模塊存在一個(gè)模塊路徑數(shù)組,存放在 module.paths 中,用于規(guī)定 Node 如何查找當(dāng)前模塊引用的自定義模塊。

具體來(lái)講,Node 會(huì)遍歷模塊路徑數(shù)組,逐個(gè)嘗試其中的路徑,查找該路徑對(duì)應(yīng)的 node_modules 目錄中是否有指定的自定義模塊,如果沒(méi)有就向上逐級(jí)遞歸,一直到根目錄下的 node_modules 目錄,直到找到目標(biāo)模塊為止,如果找不到的話就會(huì)拋出錯(cuò)誤。

可以看出,逐級(jí)向上遞歸查找 node_modules 目錄是 Node 查找自定義模塊的策略,而模塊路徑便是這個(gè)策略的具體實(shí)現(xiàn)。

同時(shí)我們也得出一個(gè)結(jié)論,在查找自定義模塊時(shí),層級(jí)越深,相應(yīng)的查找耗時(shí)就會(huì)越多。因此相比于核心模塊和普通的文件模塊,自定義模塊的加載速度是最慢的。

當(dāng)然,根據(jù)模塊路徑查找到的僅僅是一個(gè)目錄,并不是一個(gè)具體的文件,在查找到目錄后,同樣地,Node 會(huì)根據(jù)上文所描述的包處理流程進(jìn)行查找,具體過(guò)程不再贅述了。

以上是普通文件模塊和自定義模塊的文件定位的流程和需要注意的細(xì)節(jié),接下來(lái)我們來(lái)看者兩類模塊是如何編譯執(zhí)行的。

文件模塊的編譯執(zhí)行

當(dāng)定位到 require 所指向的文件后,通常模塊標(biāo)識(shí)符都不帶有擴(kuò)展名,根據(jù)上文提到的文件擴(kuò)展名分析我們可以知道,Node 支持三種擴(kuò)展名文件的編譯執(zhí)行:

  • JavaScript 文件。通過(guò) fs 模塊同步讀取文件后編譯執(zhí)行。除了 .node.json 文件,其他文件都會(huì)被當(dāng)作 .js 文件載入。

  • .node 文件,這是用 C/C++ 編寫后編譯生成的擴(kuò)展文件,Node 通過(guò) process.dlopen() 方法加載該文件。

  • json 文件,通過(guò) fs 模塊同步讀取文件后,使用 JSON.parse() 解析并返回結(jié)果。

在對(duì)文件模塊進(jìn)行編譯執(zhí)行之前,Node 會(huì)使用如下所示的模塊封裝器對(duì)其進(jìn)行包裝:

(function(exports, require, module, __filename, __dirname) {
    // 模塊代碼
});

可以看到,通過(guò)模塊封裝器,Node 將模塊包裝進(jìn)函數(shù)作用域中,與其他作用域隔離,避免變量的命名沖突、污染全局作用域等問(wèn)題,同時(shí),通過(guò)傳入 exports、require 參數(shù),使該模塊具備應(yīng)有的導(dǎo)入與導(dǎo)出能力。這便是 Node 對(duì)模塊的實(shí)現(xiàn)。

了解了模塊封裝器后,我們先來(lái)看 json 文件的編譯執(zhí)行流程。

json 文件的編譯執(zhí)行

json 文件的編譯執(zhí)行是最簡(jiǎn)單的。在通過(guò) fs 模塊同步讀取 JSON 文件的內(nèi)容后,Node 會(huì)使用 JSON.parse() 解析出 JavaScript 對(duì)象,然后將它賦給該模塊的 exports 對(duì)象,最后再返回給引用它的模塊,過(guò)程十分簡(jiǎn)單粗暴。

JavaScript 文件的編譯執(zhí)行

在使用模塊包裝器對(duì) JavaScript 文件進(jìn)行包裝后,包裝之后的代碼會(huì)通過(guò) vm 模塊的 runInThisContext()(類似 eval) 方法執(zhí)行,返回一個(gè) function 對(duì)象。

然后,將該 JavaScript 模塊的 exports、require、module 等參數(shù)傳遞給這個(gè) function 執(zhí)行,執(zhí)行之后,模塊的 exports 屬性被返回給調(diào)用方,這就是 JavaScript 文件的編譯執(zhí)行過(guò)程。

C/C++ 擴(kuò)展模塊的編譯執(zhí)行

在講解 C/C++ 擴(kuò)展模塊的編譯執(zhí)行之前,先介紹一下什么是 C/C++ 擴(kuò)展模塊。

C/C++ 擴(kuò)展模塊屬于文件模塊中的一類,顧名思義,這類模塊由 C/C++ 編寫,與 JavaScript 模塊的區(qū)別在于其加載之后不需要編譯,直接執(zhí)行之后就可以被外部調(diào)用了,因此其加載速度比 JavaScript 模塊略快。相比于用 JS 編寫的文件模塊,C/C++ 擴(kuò)展模塊明顯更具有性能上的優(yōu)勢(shì)。對(duì)于 Node 核心模塊中無(wú)法覆蓋的功能或者有特定的性能需求,用戶可以編寫 C/C++ 擴(kuò)展模塊來(lái)達(dá)到目的。

.node 文件又是什么呢,它跟 C/C++ 擴(kuò)展模塊有什么關(guān)系?

事實(shí)上,編寫好之后的 C/C++ 擴(kuò)展模塊經(jīng)過(guò)編譯之后就生成了 .node 文件。也就是說(shuō),作為模塊的使用者,我們并不直接引入 C/C++ 擴(kuò)展模塊的源代碼,而是引入 C/C++ 擴(kuò)展模塊經(jīng)過(guò)編譯之后的二進(jìn)制文件。因此,.node 文件并不需要編譯,Node 在查找到 .node 文件后,只需加載和執(zhí)行該文件即可。在執(zhí)行的過(guò)程中,模塊的 exports 對(duì)象被填充,然后返回給調(diào)用者。

值得注意的是,C/C++ 擴(kuò)展模塊編譯生成的 .node 文件在不同平臺(tái)下有不同的形式:在 *nix 系統(tǒng)下C/C++ 擴(kuò)展模塊被 g++/gcc 等編譯器編譯為動(dòng)態(tài)鏈接共享對(duì)象文件,擴(kuò)展名為 .so;在 Windows 下則被 Visual C++ 編譯器編譯為動(dòng)態(tài)鏈接庫(kù)文件,擴(kuò)展名為 .dll。但是在我們實(shí)際使用時(shí)使用的擴(kuò)展名卻是 .node,事實(shí)上 .node 的擴(kuò)展名只是為了看起來(lái)更自然一點(diǎn),實(shí)際上,在 Windows 下它是一個(gè) .dll 文件,在 *nix 下則是一個(gè) .so 文件。

Node 在查找到要 require 的 .node 文件之后,會(huì)調(diào)用 process.dlopen() 方法對(duì)該文件進(jìn)行加載和執(zhí)行。由于 .node 文件在不同平臺(tái)下是不同的文件形式,為了實(shí)現(xiàn)跨平臺(tái),dlopen() 方法在 Windows*nix 平臺(tái)下分別有不同的實(shí)現(xiàn),然后通過(guò) libuv 兼容層進(jìn)行封裝。下圖是 C/C++ 擴(kuò)展模塊在不同平臺(tái)下編譯和加載的過(guò)程:

Node中的文件模塊和核心模塊是什么

核心模塊

核心模塊在 Node 源代碼的編譯過(guò)程中,就編譯進(jìn)了二進(jìn)制執(zhí)行文件。在 Node 進(jìn)程啟動(dòng)時(shí),部分核心模塊就被直接加載進(jìn)內(nèi)存中,所以這部分核心模塊引入時(shí),文件定位和編譯執(zhí)行這兩個(gè)步驟可以省略掉,并且在路徑分析中會(huì)比文件模塊優(yōu)先判斷,所以它的加載速度是最快的。

核心模塊其實(shí)分為 C/C++ 編寫的和 JavaScript 編寫的兩部分,其中 C/C++ 文件存放在 Node 項(xiàng)目的 src 目錄下,JavaScript 文件存放在 lib 目錄下。顯然,這兩部分模塊的編譯執(zhí)行流程都有所不同。

JavaScript 核心模塊的編譯執(zhí)行

對(duì)于 JavaScript 核心模塊的編譯,在 Node 源代碼的編譯過(guò)程中,Node 會(huì)采用 V8 附帶的 js2c.py 工具,將所有內(nèi)置的 JavaScript 代碼,包括 JavaScript 核心模塊,轉(zhuǎn)換為 C++ 里的數(shù)組,JavaScript 代碼就這樣以字符串的形式存儲(chǔ)在 node 命名空間中。在啟動(dòng) Node 進(jìn)程時(shí),JavaScript 代碼就會(huì)直接加載進(jìn)內(nèi)存。

當(dāng)引入 JavaScript 核心模塊時(shí),Node 會(huì)調(diào)用 process.binding() 通過(guò)模塊標(biāo)識(shí)符分析定位到其在內(nèi)存中的位置,將其取出。在取出后,JavaScript 核心模塊同樣會(huì)經(jīng)歷模塊包裝器的包裝,然后被執(zhí)行,導(dǎo)出 exports 對(duì)象,返回給調(diào)用者。

C/C++ 核心模塊的編譯執(zhí)行

在核心模塊中,有些模塊全部由 C/C++ 編寫,有些模塊則由 C/C++ 完成核心部分,其他部分則由 JavaScript 實(shí)現(xiàn)包裝或向外導(dǎo)出,以滿足性能需求,像 buffer、fs、os 等模塊都是部分通過(guò) C/C++ 編寫的。這種 C++ 模塊主內(nèi)完成核心,JavaScript 模塊主外實(shí)現(xiàn)封裝的模式是 Node 提高性能的常見(jiàn)方式。

核心模塊中由純 C/C++ 編寫的部分稱為內(nèi)建模塊,如 node_fs、node_os 等,它們通常不被用戶直接調(diào)用,而是被 JavaScript 核心模塊直接依賴。因此,在 Node 的核心模塊的引入過(guò)程中,存在這樣一條引用鏈:

Node中的文件模塊和核心模塊是什么

那 JavaScript 核心模塊是如何加載內(nèi)建模塊的呢?

還記得 process.binding() 方法嗎,Node 通過(guò)調(diào)用該方法實(shí)現(xiàn)將 JavaScript 核心模塊從內(nèi)存中取出。該方法同樣適用于 JavaScript 核心模塊,來(lái)協(xié)助加載內(nèi)建模塊。

具體到該方法的實(shí)現(xiàn),加載內(nèi)建模塊時(shí),首先創(chuàng)建一個(gè) exports 空對(duì)象,然后調(diào)用 get_builtin_module() 方法取出內(nèi)建模塊對(duì)象,通過(guò)執(zhí)行 register_func() 填充 exports 對(duì)象,最后返回給調(diào)用方完成導(dǎo)出。這就是內(nèi)建模塊的加載和執(zhí)行過(guò)程。

通過(guò)以上分析,對(duì)于引入核心模塊這樣一條引用鏈,以 os 模塊為例,大致的流程如下:

Node中的文件模塊和核心模塊是什么

總結(jié)來(lái)說(shuō),引入 os 模塊的過(guò)程經(jīng)歷 JavaScript 文件模塊的引入、JavaScript 核心模塊的加載和執(zhí)行和內(nèi)建模塊的加載執(zhí)行,過(guò)程十分繁瑣復(fù)雜,但是對(duì)于模塊的調(diào)用者來(lái)說(shuō),由于屏蔽了底層的復(fù)雜實(shí)現(xiàn)和細(xì)節(jié),僅僅通過(guò) require() 就可完成整個(gè)模塊的導(dǎo)入,十分簡(jiǎn)潔。友好。

undefined

“Node中的文件模塊和核心模塊是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI