Node 模塊引入

Node 模塊引入

Node 模塊引入主要會經歷如下3個步驟:

  • 路徑分析
  • 文件定位
  • 編譯執行

在 Node 中,模塊分爲核心模塊和文件模塊。

核心模塊是 Node 提供的模塊。其中部分模塊在 Node 源碼編譯時直接生成二進制執行文件,在 Node 進程啓動時被加載到內存中,因此當這部分模塊引入時,會只直接跳過文件定位和編譯執行,且在路徑分析中優先判斷,它的加載速度最快。

文件模塊是用戶編寫的模塊。文件模塊在運行時動態加載,需要完整的路徑分析、文件定位、編譯執行過程,速度比核心模塊慢。

緩存優先

跟瀏覽器會緩存靜態腳本文件以提高性能一樣,Node 對引入的模塊也會進行緩存,以減少二次引入的開銷。不同之處在於,瀏覽器是緩存文件,而 Node 是緩存編譯和執行之後的對象。

不論是核心模塊還是文件模塊,require() 方法對相同的模塊的二次加載一律採用緩存優先的方式。

路徑分析和文件定位

require() 方法接受一個標識符作爲參數,標識符有幾種形式,根據不同的標識符,模塊的查找和定位有不同的差異。

模塊標識符分析

模塊標識符主要分爲以下幾類:

  • 核心模塊,如 http、fs、path 等
  • . 或 … 開頭的相對路徑文件模塊
  • 以 / 開頭的絕對路徑文件模塊
  • 非路徑形式的文件模塊,如自定義的 connect 模塊

核心模塊

核心模塊的優先級僅次於緩存加載。

如果要加載一個與核心模塊標識符相同的自定義模塊,那是不會成功的。如果要加載必須選擇一個不同的標識符或換成路徑方式。

路徑形式的文件模塊

以 .、… 和 / 開頭的標識符,這裏都被當作文件模塊處理。在分析文件模塊時,require() 方法會將路徑轉爲真實路徑,並以真實路徑作爲索引,將編譯後的結果存放到緩存中。

由於文件模塊給 Node 指明瞭確切的文件位置,所以在查找過程中可以節約大量時間,其加載時間慢於核心模塊。

自定義模塊

自定義模塊是一種特殊的文件模塊,可以是一個文件或者包的形式。這類模塊的查找是最費時的,是所有方式中最慢的。

在介紹自定義模塊的查找方式之前,需要先介紹一下模塊路徑這個概念。

模塊路徑是 Node 在定位文件模塊的具體文件時制定的查找策略,具體表現爲一個路徑組成的數組。關於這個路徑的生成規則,我們可以手動嘗試一番。

(1) 創建 module_path.js 文件,其內容如下:

console.log(module.paths);

(2) 將該文件放到任意一個目錄中,然後執行如下命令:

node module_path.js

在 Linux 下,你可能得到如下的數組輸出:

['/home/user/demo/node_modules',
'/home/user/node_modules',
'/home/node_modules',
'/node_modules']

在 Windows 下,可能是這樣:

['c:\\demo\\node_modules',
'c:\\node_modules']

可以看出,模塊路徑的生成規則如下所示:

  1. 當前文件目錄下的 node_modules 目錄;
  2. 父目錄下的 node_modules 目錄;
  3. 父目錄下的父目錄下的 node_modules 目錄;
  4. 沿着路徑向上逐級遞歸,直到根目錄下的 node_modules 目錄。

在自定義模塊的加載過程中,Node 會逐個嘗試模塊路徑中的路徑,直到找到目標文件爲止。可以看出,當前文件的路徑越深,模塊查找耗時會越多,這是自定義模塊的加載速度最慢的原因。

文件定位

在文件定位過程中,有些細節需要注意,主要包括文件擴展名的分析、目錄和包的處理。

文件擴展名分析

require() 在分析標識符的過程中,會出現標識符中不包含文件擴展名的情況。CommonJS 模塊規範也允許在標識符中不包含文件擴展名,這種情況下,Node 會按 .js、.json、.node 的次序補足擴展名,依次嘗試。

在嘗試的過程中,需要調用 fs 模塊同步阻塞式的判斷文件是否存在。因爲 Node 是單線程的,所以這裏是一個會引起性能問題的地方。所以,如果是 .json 和 .node 文件,在標識符中帶上擴展名,會加快一點速度。

目錄分析和包

在分析標識符的過程中,require() 通過分析文件擴展名後,可能沒有查找到對應的文件,但卻得到一個目錄,這在引入自定義模塊和逐個模塊路徑查找時經常會出現,此時 Node 會將目錄當做一個包來處理。

在這個過程中,Node 對 CommonJS 包規範進行了一定程度的支持。首先,Node 在當前目錄下查找 package.json(CommonJS 包規範定義的包描述文件),通過JSON.parse() 解析出包描述對象,從中取出 main 屬性指定的文件名進行定位。如果文件名缺少擴展名,將會進入擴展名分析的步驟。

而如果 main 屬性指定的文件名錯誤,或者壓根沒有 package.json 文件,Node 會將 index 當作默認文件名,然後依次查找 index.js、index.json、index.node。

如果在目錄分析的過程中沒有定位成功任何文件,則自定義模塊進入下一個模塊路徑進行查找。如果模塊路徑數組都被遍歷完畢,依然沒有查找到目標文件,則會拋出查找失敗的異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章