因為標識符有幾種形式,對于不同的標識符,模塊的查找和定位有不同程度上的差異。
## 1.模塊標識符分析
前面提到過,require()方法接受一個標識符作為參數。在Node實現中,正是基于這樣一個標識符進行模塊查找的。模塊標識符在Node中主要分為以下幾類:
* 核心模塊,如http、fs、path等。
* . 或 .. 開始的相對路徑文件模塊。
* 以 / 開始的絕對路徑文件模塊。
* 非路徑形式的文件模塊,如自定義的connect模塊。
### 核心模塊
核心模塊的優先級僅次于緩存加載,它在Node的源代碼編譯過程中已編譯為二進制代碼,其加載過程最快。
如果試圖加載一個與核心模塊標識符相同的自定義模塊,那是不會成功的。如果自己編寫了一個 http 用戶模塊,要想加載成功,必須選擇一個不同的標識符或者換用路徑的方式。
### 路徑形式的文件模塊
以 . 、..和 / 開始的標識符,這里都被當作文件模塊來處理。在分析路徑模塊時,require() 方法會將路徑轉換為真實路徑,并以真實路徑作為索引,將編譯執行后的結果存放到緩存中,以使二次加載時更快。
由于文件模塊給Node指明了確切的文件位置,所以在查找過程中可以節約大量時間,其加載速度慢于核心模塊。
### 自定義模塊
自定義模塊指的是非核心模塊,也不是路徑形式的標識符。它是一種特殊的文件模塊,可能是一個文件或者包的形式。這類模塊的查找是最費時的,也是所有方式中最慢的一種。
在介紹自定義模塊的查找方式之前,需要先介紹一下模塊路徑這個概念。
模塊路徑是Node在定位文件模塊的具體文件時制定的查找策略,具體表現為一個路徑組成的數組。關于這個路徑的生成規則,我們可以手動嘗試一番。
(1). 創建 `module_path.js` 文件,其內容為 `console.log(module.paths);` 。
(2). 將其放入任意一個目錄中然后執行 node module_path.js 。
在Linux下,你可能得到這樣一個數組輸出:
~~~
[ '/home/jackson/research/node_modules',
'/home/jackson/node_modules',
'/home/node_modules',
'/node_modules' ]
~~~
而在Windows下,也許是這樣:
~~~
[ 'C:\\Users\\Simon\\Desktop\\NodeDemo\\node_modules',
'C:\\Users\\Simon\\Desktop\\node_modules',
'C:\\Users\\Simon\\node_modules',
'C:\\Users\\node_modules',
'C:\\node_modules' ]
~~~
可以看出,模塊路徑的生成規則如下:
* 當前文件目錄下的node_modules目錄
* 父目錄下的node_modules目錄
* 父目錄的父目錄下的node_modules目錄
* 沿路徑向上逐級遞歸,直到根目錄下的node_modules目錄
它的生成方式與JavaScript的原型鏈或作用域鏈的查找方式十分類似。在加載的過程中,Node會逐個嘗試模塊路徑中的路徑,直到找到目標文件為止。可以看出,當前文件的路徑越深,模塊查找耗時越多,這是自定義模塊的加載速度最緩慢的原因。
## 2.文件定位
從緩存加載的優化策略使得二次引入時不需要路徑分析、文件定位和編譯執行的過程,大大提高了再次加載模塊時的效率。
但在文件定位的過程中,還有一些細節需要注意,這主要包括文件擴展名的分析、目錄和包的處理。
### 文件擴展名分析
require()在分析標識符的過程中,會出現標識符中不包含文件擴展名的情況。CommonJS模塊規范也允許在標識符中不包含文件擴展名,這種情況下,Node會按.js、.json、.node的次序補足擴展名,依次嘗試。
在嘗試的過程中,需要調用fs模塊同步阻塞式的判斷文件是否存在。因為Node是單線程的,所以這里是一個會引起性能問題的地方。小訣竅是:如果是.node或者.json文件,在傳遞給require()方法的標識符中帶上擴展名,會加快一點速度。另一個訣竅是:同步配合緩存,可以大幅度緩解Node單線程中阻塞式調用的缺陷。
### 目錄分析和包
在分析標識符的過程中,require()通過分析文件擴展名之后,可能沒有查找到對應文件,但卻得到一個目錄,這在引入自定義模塊和逐個模塊路徑進行查找時會經常出現,此時Node會將目錄當作一個包來處理。
在這個過程中,Node對CommonJS包規范進行了一定程度的支持。首先,Node在當前目錄下查找 package.json(CommonJS包規范定義的包描述文件),通過JSON.parse()解析出包描述對象,從中取出main屬性指定的文件名錯誤,或者壓根沒有package.json文件,Node會將index當作默認文件名,然后依次查找index.js、index.json、index.node 。
如果在目錄分析的過程中沒有定位成功任何文件,則自定義模塊進入下一個模塊路徑進行查找。如果模塊路徑數組都被遍歷完畢,依然沒有找到任何目標文件,則會拋出查找失敗的異常。
- 目錄
- 第1章 Node 簡介
- 1.1 Node 的誕生歷程
- 1.2 Node 的命名與起源
- 1.2.1 為什么是 JavaScript
- 1.2.2 為什么叫 Node
- 1.3 Node給JavaScript帶來的意義
- 1.4 Node 的特點
- 1.4.1 異步 I/O
- 1.4.2 事件與回調函數
- 1.4.3 單線程
- 1.4.4 跨平臺
- 1.5 Node 的應用場景
- 1.5.1 I/O 密集型
- 1.5.2 是否不擅長CPU密集型業務
- 1.5.3 與遺留系統和平共處
- 1.5.4 分布式應用
- 1.6 Node 的使用者
- 1.7 參考資源
- 第2章 模塊機制
- 2.1 CommonJS 規范
- 2.1.1 CommonJS 的出發點
- 2.1.2 CommonJS 的模塊規范
- 2.2 Node 的模塊實現
- 2.2.1 優先從緩存加載
- 2.2.2 路徑分析和文件定位
- 2.2.3 模塊編譯
- 2.3 核心模塊
- 2.3.1 JavaScript核心模塊的編譯過程
- 2.3.2 C/C++核心模塊的編譯過程
- 2.3.3 核心模塊的引入流程
- 2.3.4 編寫核心模塊
- 2.4 C/C++擴展模塊
- 2.4.1 前提條件
- 2.4.2 C/C++擴展模塊的編寫
- 2.4.3 C/C++擴展模塊的編譯
- 2.4.2 C/C++擴展模塊的加載
- 2.5 模塊調用棧
- 2.6 包與NPM
- 2.6.1 包結構
- 2.6.2 包描述文件與NPM
- 2.6.3 NPM常用功能
- 2.6.4 局域NPM
- 2.6.5 NPM潛在問題
- 2.7 前后端共用模塊
- 2.7.1 模塊的側重點
- 2.7.2 AMD規范
- 2.7.3 CMD規范
- 2.7.4 兼容多種模塊規范
- 2.8 總結
- 2.9 參考資源
- 第3章 異步I/O
- 3.1 為什么要異步I/O
- 3.1.1 用戶體驗
- 3.1.2 資源分配
- 3.2 異步I/O實現現狀
- 3.2.1 異步I/O與非阻塞I/O
- 3.2.2 理想的非阻塞異步I/O
- 3.2.3 現實的異步I/O
- 3.3 Node的異步I/O
- 3.3.1 事件循環
- 3.3.2 觀察者
- 3.3.3 請求對象
- 3.3.4 執行回調
- 3.3.5 小結
- 3.4 非I/O的異步API
- 3.4.1 定時器
- 3.5 事件驅動與高性能服務器