Nodejs(模塊機(jī)制)

在Node 中引入模塊,需要經(jīng)歷三個(gè)步驟

  1. 路徑分析
  2. 文件定位
  3. 編譯執(zhí)行
    在Node中,模塊分為兩類:一類是Node提供的模塊,稱為核心模塊;一類是用戶編寫的模塊,稱為文件模塊。
  • 核心模塊部分在Node源碼的編譯過(guò)程中,編譯進(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í)行過(guò)程,速度比核心模塊慢。

優(yōu)先從緩存加載

無(wú)論是核心模塊還是文件模塊,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源碼編譯過(guò)程中已經(jīng)編譯為二進(jìn)制代碼,其加載過(guò)程最快

路徑形式的文件模塊

在分析路徑模塊時(shí),require()方法會(huì)將路徑轉(zhuǎn)換為真實(shí)的路徑,并以真實(shí)路徑作為索引,將編譯執(zhí)行后的結(jié)果放在緩存中,文件模塊給Node 指明了確切的文件位置,所以查找過(guò)程中可以節(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)在這里,可以通過(guò)npm root -g查看路徑)

文件定位

文件擴(kuò)展名分析

require()在分析標(biāo)識(shí)符的過(guò)程中,允許在標(biāo)識(shí)符中不包含文件擴(kuò)展名,這種情況下Node會(huì)按照.js、.json、.node的次序補(bǔ)足擴(kuò)展名

目錄分析和包

在分析標(biāo)識(shí)符的過(guò)程中,require()通過(guò)分析文件擴(kuò)展名之后,可能沒(méi)有查找到對(duì)應(yīng)的文件卻得到了一個(gè)目錄,此時(shí)Node會(huì)將目錄當(dāng)作一個(gè)包來(lái)處理。

包處理規(guī)則:Node在當(dāng)前目錄查找package.json文件,通過(guò)JSON.parse()解析出包的描述對(duì)象,從中取出main屬性指定的文件名進(jìn)行定位,如果文件名缺少擴(kuò)展名,會(huì)進(jìn)入擴(kuò)展名分析步驟,如果main指定的文件名錯(cuò)誤,或沒(méi)有package.json文件,Node會(huì)將index當(dāng)作默認(rèn)文件名,然后依次查找index.js、index.json、index.node,如果目錄分析中沒(méi)有定位到任何文件,在自定義模塊進(jìn)入下一個(gè)模塊的路徑分析,如果模塊的路徑數(shù)組都遍歷完依然沒(méi)有找的目標(biāo)文件,則會(huì)拋出查找失敗的異常。

模塊編譯

去看源碼
Node會(huì)新建一個(gè)模塊對(duì)象,然后根據(jù)路徑載入并編譯,對(duì)于不同的文件擴(kuò)展名,載入的方式也不同。

  • .js 文件。通過(guò)fs模塊同步讀取文件后編譯執(zhí)行
  • .node 文件。這是用c++編寫的擴(kuò)展文件,通過(guò)dlopen()方法加載最后編譯生成的文件
  • .json文件。通過(guò)fs模塊同步讀取文件后,用JSON.parse()解析返回結(jié)果
  • 其余文件均當(dāng)作js文件引入

每一個(gè)編譯成功的模塊都會(huì)將其文件路徑作為索引緩存在Module._cache對(duì)象上。
其中,Module._extensions 會(huì)被賦值給require()extensions屬性。通過(guò)console(require.extensions)可以查看已有的加載方式;

JavaScript模塊的編譯

在編譯過(guò)程中,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"
}

通過(guò)全局安裝的模塊都會(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í)加載,換句話說(shuō)是在 NodeJS 腳本執(zhí)行時(shí)才加載進(jìn)來(lái)

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)鏈接,或者說(shuō)建立了一個(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);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 說(shuō)明:該學(xué)習(xí)筆記參考《深入淺出Node.js》在學(xué)習(xí)過(guò)程中,添加了自己的理解和適當(dāng)?shù)难a(bǔ)充!僅供參考! NodeJs...
    秋意思寒閱讀 759評(píng)論 0 1
  • 之前 ECMAScript 的問(wèn)題: 沒(méi)有模塊系統(tǒng),標(biāo)準(zhǔn)庫(kù)較少(如文件系統(tǒng)等缺失API),沒(méi)有標(biāo)準(zhǔn)接口,無(wú)包管理系...
    Air_cc閱讀 392評(píng)論 0 0
  • 早在Netscape誕生不久后,JavaScript就一直在探索本地編程的路,Rhino是其代表產(chǎn)物。無(wú)奈那時(shí)服務(wù)...
    itsmyturn閱讀 365評(píng)論 0 0
  • 1.為什么要CommonJS規(guī)范 javascript存在的缺點(diǎn) 沒(méi)有模塊系統(tǒng) 標(biāo)準(zhǔn)庫(kù)比較少 沒(méi)有標(biāo)準(zhǔn)接口 缺乏包...
    maikuraki閱讀 249評(píng)論 0 1
  • 明知道自己AB型血,呼吸道脆弱。還是忍不住打掃自己的老屋子。最后難受好幾天…… 這就感性大于理智的結(jié)果吧!
    與洛閱讀 174評(píng)論 0 1

友情鏈接更多精彩內(nèi)容