在Node 中引入模塊刨沦,需要經(jīng)歷三個(gè)步驟
- 路徑分析
- 文件定位
- 編譯執(zhí)行
在Node中诗宣,模塊分為兩類:一類是Node提供的模塊,稱為核心模塊;一類是用戶編寫的模塊想诅,稱為文件模塊召庞。
核心模塊部分在Node源碼的編譯過程中,編譯進(jìn)了二進(jìn)制執(zhí)行文件来破。在Node進(jìn)程啟動(dòng)時(shí)篮灼,部分核心模塊就被直接加載進(jìn)內(nèi)存中,所以這部分核心模塊引入時(shí)徘禁,文件定位和編譯執(zhí)行兩個(gè)步驟可以省略掉诅诱,并且在路徑分析中優(yōu)先判斷,所以它的加載速度是最快的送朱。
文件模塊則是在運(yùn)行時(shí)動(dòng)態(tài)加載娘荡,需要完整的路徑分析、文件定位骤菠、編譯執(zhí)行過程它改,速度比核心模塊慢。
優(yōu)先從緩存加載
無論是核心模塊還是文件模塊商乎,require()
方法對(duì)相同模塊的二次加載都一律采用緩存優(yōu)先的方式央拖,不同之處在于核心模塊的緩存檢查優(yōu)先于文件模塊的緩存檢查。
路徑分析和文件定位
模塊標(biāo)識(shí)符分析
require()
方法接受一個(gè)標(biāo)識(shí)符作為參數(shù)鹉戚。標(biāo)識(shí)符在Node中主要分為一下幾類:
核心模塊鲜戒,例如http、fs抹凳、path等
.或..開始的相對(duì)路徑文件模塊
以/開始的絕對(duì)路徑文件模塊
非路徑形式的文件模塊,例如自定義模塊
核心模塊
核心模塊加載優(yōu)先級(jí)僅次于緩存赢底,它在Node源碼編譯過程中已經(jīng)編譯為二進(jìn)制代碼失都,其加載過程最快
路徑形式的文件模塊
在分析路徑模塊時(shí)柏蘑,require()
方法會(huì)將路徑轉(zhuǎn)換為真實(shí)的路徑粹庞,并以真實(shí)路徑作為索引肖粮,將編譯執(zhí)行后的結(jié)果放在緩存中漫试,文件模塊給Node 指明了確切的文件位置,所以查找過程中可以節(jié)約大量時(shí)間商虐,其加載速度慢于核心模塊
自定義模塊
自定義模塊指的是非核心模塊典勇,也不是路徑形式的標(biāo)識(shí)符。它是一種特殊的文件模塊叮趴,可能是一個(gè)文件或者包的形式割笙,這類模塊是加載最慢的一種。
*** Node 定位文件模塊的查找策略*** 具體表現(xiàn)為一個(gè)路徑組成的數(shù)組眯亦∩烁龋可以手動(dòng)嘗試一下:
在任意目錄下執(zhí)行輸入node進(jìn)入node環(huán)境,然后輸入module.paths
在mac 下會(huì)得到下面一個(gè)數(shù)組輸出
> module.paths
[ '/Users/fanrongrong/repl/node_modules',
'/Users/fanrongrong/node_modules',
'/Users/node_modules',
'/node_modules',
'/Users/fanrongrong/.node_modules',
'/Users/fanrongrong/.node_libraries',
'/Users/fanrongrong/.nvm/versions/node/v8.9.3/lib/node' ]
>
模塊路徑的生成規(guī)則
當(dāng)前文件目錄下的node_modules目錄
父目錄下的node_modules目錄
父目錄的父目錄下的node_modules目錄妻率,沿著路徑逐級(jí)遞歸乱顾,直到跟目錄
mac 會(huì)查找用戶模塊下的.node_modules和.node_libraries 目錄,window會(huì)查找環(huán)境變量$HOME下的這兩個(gè)目錄
node 的安裝目錄下的node_modules (全局安裝的包默認(rèn)在這里宫静,可以通過npm root -g查看路徑)
文件定位
文件擴(kuò)展名分析
require()
在分析標(biāo)識(shí)符的過程中走净,允許在標(biāo)識(shí)符中不包含文件擴(kuò)展名,這種情況下Node會(huì)按照.js孤里、.json伏伯、.node的次序補(bǔ)足擴(kuò)展名
目錄分析和包
在分析標(biāo)識(shí)符的過程中,require()
通過分析文件擴(kuò)展名之后捌袜,可能沒有查找到對(duì)應(yīng)的文件卻得到了一個(gè)目錄说搅,此時(shí)Node會(huì)將目錄當(dāng)作一個(gè)包來處理。
包處理規(guī)則:Node在當(dāng)前目錄查找package.json文件虏等,通過JSON.parse()解析出包的描述對(duì)象,從中取出main屬性指定的文件名進(jìn)行定位弄唧,如果文件名缺少擴(kuò)展名适肠,會(huì)進(jìn)入擴(kuò)展名分析步驟,如果main指定的文件名錯(cuò)誤候引,或沒有package.json文件迂猴,Node會(huì)將index當(dāng)作默認(rèn)文件名,然后依次查找index.js、index.json背伴、index.node,如果目錄分析中沒有定位到任何文件峰髓,在自定義模塊進(jìn)入下一個(gè)模塊的路徑分析傻寂,如果模塊的路徑數(shù)組都遍歷完依然沒有找的目標(biāo)文件,則會(huì)拋出查找失敗的異常携兵。
模塊編譯
去看源碼
Node會(huì)新建一個(gè)模塊對(duì)象疾掰,然后根據(jù)路徑載入并編譯,對(duì)于不同的文件擴(kuò)展名徐紧,載入的方式也不同静檬。
- .js 文件。通過fs模塊同步讀取文件后編譯執(zhí)行
- .node 文件并级。這是用c++編寫的擴(kuò)展文件拂檩,通過
dlopen()
方法加載最后編譯生成的文件 - .json文件。通過fs模塊同步讀取文件后嘲碧,用
JSON.parse()
解析返回結(jié)果 - 其余文件均當(dāng)作js文件引入
每一個(gè)編譯成功的模塊都會(huì)將其文件路徑作為索引緩存在Module._cache對(duì)象上稻励。
其中,Module._extensions 會(huì)被賦值給require()
的extensions
屬性愈涩。通過console(require.extensions)
可以查看已有的加載方式望抽;
JavaScript模塊的編譯
在編譯過程中,Node對(duì)獲取的JavaScript 文件內(nèi)容進(jìn)行包裝履婉,如下:
(function (exports, require, module, __filename, __dirname) {
var math = require('math')
exports.area = function () {
return Math.PI * radius * radius2
}
})
包與NPM
包結(jié)構(gòu)
- package.json: 包描述文件
- bin : 存放可執(zhí)行二進(jìn)制文件目錄
- lib: 存放JavaScript 代碼的目錄
包描述文件
- main 模塊引入方法require方法會(huì)優(yōu)先檢查這個(gè)字段煤篙,并將它入口,如果不存在就查找index.js毁腿、index.node辑奈、index.json
- bin 配置好bin 字段當(dāng)npm install 包名 -g 時(shí)可以將腳本添加到執(zhí)行路徑中
安裝依賴包
全局安裝模式
- 根據(jù)bin字段的配置,將實(shí)際的腳本鏈接到與node 可執(zhí)行文件相同的路徑下:
"bin": {
"express": "./bin/express"
}
通過全局安裝的模塊都會(huì)被安裝到一個(gè)統(tǒng)一目錄下狸棍,一搬都是node的安裝目錄下的lib/node_modules 下
node的包引用
node commonjs 規(guī)范引用包方式
node 在8.0版之前都是遵循commenjs 規(guī)范進(jìn)行的包引用(exports/module.exprots, require)
exports是module.exports 的一個(gè)引用身害,所以導(dǎo)出時(shí)可以使用exports.xxxx = xxx 的方式,而不能使用exports = xxxx的方式草戈,可以使用module.exports = xxx的方式
這種方式是運(yùn)行時(shí)加載塌鸯,換句話說是在 NodeJS 腳本執(zhí)行時(shí)才加載進(jìn)來
node 8.0 之后加入ES方式的引用包方式(import, export)
使用ES方式需要在啟動(dòng)node時(shí)加入?yún)?shù) --experimental-modules
這種方式引用是在靜態(tài)分析時(shí)候就確定了引用關(guān)系,就像目標(biāo)模塊建立了一個(gè)符號(hào)鏈接唐片,或者說建立了一個(gè)指針丙猬。這種加載方式加載效率應(yīng)該略高于 CommonJS涨颜。
例子:
// a.js
var n = 3;
exports.n = 3
exports.add = () => {
n++;
}
// b.js
var mod = require('./a.js');
console.log(mod.n);
mod.add();
console.log(mod.n);
// a.mjs
export let n = 3;
export let add = () => {
n++;
}
// b.mjs
import {
n,
add
} from './a.mjs'
console.log(n);
add();
console.log(n);