什么是模塊?
node.js通過實現CommonJS的Modules/1.0標准引入了模塊(module)概念,模塊是Node.js的基本組成部分.一個node.js文件就是一個模塊,也就是說文件和模塊是一一對應的關系.這個文件可以是JavaScript代碼,JSON或者編譯過的C/C++擴展.
Node.js的模塊分為兩類,一類為原生(核心)模塊,一類為文件模塊。
在文件模塊中,又分為3類模塊。這三類文件模塊以后綴來區分,Node.js會根據后綴名來決定加載方法。
- .js。通過fs模塊同步讀取js文件並編譯執行。
- .node。通過C/C++進行編寫的Addon。通過dlopen方法進行加載。
- .json。讀取文件,調用JSON.parse解析加載。
Node.提供了exports和require兩個對象,其中exports是模塊公開的接口,require用於從外部獲取一個模塊接口,即所獲取模塊的exports對象.
require查找策略
原生模塊在Node.js源代碼編譯的時候編譯進了二進制執行文件,加載的速度最快。另一類文件模塊是動態加載的,加載速度比原生模塊慢。但是Node.js對原生模塊和文件模塊都進行了緩存,於是在第二次require時,是不會有重復開銷的。盡管require方法極其簡單,但是內部的加載卻是十分復雜的,其加載優先級也各自不同。
require方法接受以下幾種參數的傳遞:
- http、fs、path等,原生模塊。
- ./mod或../mod,相對路徑的文件模塊。
- /pathtomodule/mod,絕對路徑的文件模塊。
- mod,非原生模塊的文件模塊。
當require一個文件模塊時,從當前文件目錄開始查找node_modules目錄;然后依次進入父目錄,查找父目錄下的node_modules目錄;依次迭代,直到根目錄下的node_modules目錄。
簡而言之,如果require絕對路徑的文件,查找時不會去遍歷每一個node_modules目錄,其速度最快。其余流程如下:
- 從module path數組中取出第一個目錄作為查找基准。
- 直接從目錄中查找該文件,如果存在,則結束查找。如果不存在,則進行下一條查找。
- 嘗試添加.js、.json、.node后綴后查找,如果存在文件,則結束查找。如果不存在,則進行下一條。
- 嘗試將require的參數作為一個包來進行查找,讀取目錄下的package.json文件,取得main參數指定的文件。
- 嘗試查找該文件,如果存在,則結束查找。如果不存在,則進行第3條查找。
- 如果繼續失敗,則取出module path數組中的下一個目錄作為基准查找,循環第1至5個步驟。
- 如果繼續失敗,循環第1至6個步驟,直到module path中的最后一個值。
- 如果仍然失敗,則拋出異常。
何為核心? 重要的/不可缺的!
node.js作為一門跨平台服務器端編程語言,必然也有它的核心.
node.js繼承了javascript 客戶端語言該有的優勢,同時摒棄了客戶端javascript的一些缺點,比如在客戶端javascript環境下,全局變量可以到處被定義,隨意被覆蓋,代碼污染嚴重,所以node.js有了模塊的概念,在模塊里定義的全局變量如果沒有被export ,那么此變量是私有的,只能在定義的模塊是使用.
不用提醒我,我並沒有跑題,正因為模塊機制,導致node.js的一些核心模塊也編譯成各自獨立的二進制文件,他們就放在 node 源代碼中 lib 文件夾下.
為啥是二進制文件?你不用吃驚,reqire 可以引入 核心二進制模塊,第三方模塊,js文件,json文件及編譯的好的c++模塊(擴展名.node)
我們上面提到核心的就是重要的/不可缺的. 比如 'http' 模塊, 'fs' 模塊,這些內置的核心模塊有優先載入的權限,也就是說,如果你自己創建了一個 http.js 的文件,然后通過require('http') 來引用的話,系統會優先把內置的 http 模塊加載進來,而對於你這個同名的文件根本不理不睬.
如此看來,node的加載機制是有順序的,我們不妨來理一下.
(1)首先加載核心模塊
(2)試圖在require 的名稱后面加上.js 去搜索並加載.
(3)試圖在require 的名稱后面加上.json 去搜索並加載.
(4)試圖在require 的名稱后面加上 .node 去搜索並加裝編譯好的c++模塊.
在加載文件模塊時我們提到了搜索,如果搜索,node遵循什么規則?
常見的加載方式有3種類型
require('http');
require('./dbApi');
require('express');
上面列出的三種加載方式其實順便也給出了加載順序規則.
首先加載核心模塊,不管有沒有同名/同目錄的情況下,核心模塊優先加載.
注意:核心模塊在node.js安裝是已經被編譯成二進制模塊,程序啟動時已經被自動加載到系統內存中,所以速度快,效率高.
其次按照相對路徑/絕對路徑加載文件模塊(加載順序,首先試圖按照路徑查找 .js 擴展名的文件,如果沒有,試圖按照路徑查找 .json 擴展名的文件,如果還是沒有,就按照路徑查找 .node 擴展名的c++模塊了)
注意:按照common.js 規范指示,模塊加載過程中,模塊名需要遵守小駝峰命名規則,且最好不帶擴展名,但是上面三種文件中,因為有優先搜索規則,而文件io操作都是同步過程,也就是說,如果你要加載的是一個 config.json 文件,那么你的require('./config') 這樣寫導致系統首先在同層目錄下搜索config.js 文件,直到系統搜索失敗后才會繼續搜索 config.json 文件,而這個由於IO同步操作會導致加載模塊延遲,所以當你要加載 .json文件或者 .node 文件時,最好加上擴展名.
最后搜索 node_modules 目錄下通過npm下載的第三方模塊.
注意:首次加載這類模塊最慢,因為執行文件所在目錄的node_mondel 文件夾下找不到時,會去父級node_mondel 文件夾里查找,如果還是找不到會去父級的父級node_mondel 文件夾里查找.......但是,只要首次加載成功后,node就會緩存起來,它緩存的是編譯后的二進制模塊,所以以后的加載速度和效率都的有保證的.