Node.js 模塊機(jī)制采用了 Commonjs 規(guī)范顽素,彌補(bǔ)了當(dāng)前 JavaScript 開發(fā)大型應(yīng)用沒有標(biāo)準(zhǔn)的缺陷礁竞,類似于 Java 中的類文件,Python 中的 import 機(jī)制厘擂,Node.js 中可以通過 module.exports程腹、require 來導(dǎo)出和引入一個(gè)模塊.
在模塊加載機(jī)制中,Node.js 采用了延遲加載的策略舅逸,只有在用到的情況下桌肴,系統(tǒng)模塊才會(huì)被加載,加載完成后會(huì)放到 binding_cache 中琉历。
面試指南
require的加載機(jī)制坠七?
,參考:模塊加載機(jī)制module.exports與exports的區(qū)別
旗笔,參考:對(duì)象引用關(guān)系考察假設(shè)有a.js彪置、b.js兩個(gè)模塊相互引用,會(huì)有什么問題蝇恶?是否為陷入死循環(huán)拳魁?
,參考正文“模塊循環(huán)引用問題1”a模塊中的undeclaredVariable變量在b.js中是否會(huì)被打哟榛 潘懊?
,參考正文“模塊循環(huán)引用問題2”模塊在require的過程中是同步還是異步贿衍?
授舟,參考正文模塊加載機(jī)制 “文件模塊“
模塊的分類
系統(tǒng)模塊
C/C++ 模塊,也叫 built-in 內(nèi)建模塊舌厨,一般用于 native 模塊調(diào)用岂却,在 require 出去
native 模塊,在開發(fā)中使用的 Node.js 的 http、buffer躏哩、fs 等署浩,底層也是調(diào)用的內(nèi)建模塊 (C/C++)。
第三方模塊
非 Node.js 自帶的模塊稱為第三方模塊扫尺,其實(shí)還分為路徑形式的文件模塊(以 .
筋栋、 ..
、 /
開頭的)和自定義的模塊(比如 express正驻、koa 框架弊攘、moment.js 等)
javaScript 模塊:例如
hello.js
json 模塊:例如
hello.json
C/C++ 模塊:編譯之后擴(kuò)展名為 .node 的模塊,例如
hello.node
目錄結(jié)構(gòu)
<pre>├── benchmark 一些 Node.js 性能測(cè)試代碼
├── deps Node.js 依賴
├── doc 文檔
├── lib Node.js 對(duì)外暴露的 js 模塊源碼
├── src Node.js 的 c/c++ 源碼文件姑曙,內(nèi)建模塊
├── test 單元測(cè)試
├── tools 編譯時(shí)用到的工具
├── doc api 文檔
├── vcbuild.bat win 平臺(tái) makefile 文件
├── node.gyp node-gyp 構(gòu)建編譯任務(wù)的配置文件
...
</pre>
模塊加載機(jī)制
面試中可能會(huì)問到能說下require的加載機(jī)制嗎?
在 Node.js 中模塊加載一般會(huì)經(jīng)歷 3 個(gè)步驟襟交, 路徑分析
、 文件定位
伤靠、 編譯執(zhí)行
捣域。
按照模塊的分類,按照以下順序進(jìn)行優(yōu)先加載:
系統(tǒng)緩存:模塊被執(zhí)行之后會(huì)會(huì)進(jìn)行緩存宴合,首先是先進(jìn)行緩存加載焕梅,判斷緩存中是否有值。
系統(tǒng)模塊:也就是原生模塊卦洽,這個(gè)優(yōu)先級(jí)僅次于緩存加載贞言,部分核心模塊已經(jīng)被編譯成二進(jìn)制,省略了
路徑分析
阀蒂、文件定位
该窗,直接加載到了內(nèi)存中,系統(tǒng)模塊定義在 Node.js 源碼的 lib 目錄下蚤霞,可以去查看挪捕。文件模塊:優(yōu)先加載
.
、..
争便、/
開頭的级零,如果文件沒有加上擴(kuò)展名,會(huì)依次按照.js
滞乙、.json
奏纪、.node
進(jìn)行擴(kuò)展名補(bǔ)足嘗試,那么在嘗試的過程中也是以同步阻塞模式來判斷文件是否存在斩启,從性能優(yōu)化的角度來看待序调,.json
、.node
最好還是加上文件的擴(kuò)展名兔簇。目錄做為模塊:這種情況發(fā)生在文件模塊加載過程中发绢,也沒有找到硬耍,但是發(fā)現(xiàn)是一個(gè)目錄的情況,這個(gè)時(shí)候會(huì)將這個(gè)目錄當(dāng)作一個(gè)
包
來處理边酒,Node 這塊采用了 Commonjs 規(guī)范经柴,先會(huì)在項(xiàng)目根目錄查找 package.json 文件,取出文件中定義的 main 屬性("main":"lib/hello.js")
描述的入口文件進(jìn)行加載墩朦,也沒加載到坯认,則會(huì)拋出默認(rèn)錯(cuò)誤: Error: Cannot find module 'lib/hello.js'node_modules 目錄加載:對(duì)于系統(tǒng)模塊、路徑文件模塊都找不到氓涣,Node.js 會(huì)從當(dāng)前模塊的父目錄進(jìn)行查找牛哺,直到系統(tǒng)的根目錄
require 模塊加載時(shí)序圖
模塊緩存在哪
上面講解了模塊的加載機(jī)制,中間有提到模塊初次加載之后會(huì)緩存起來劳吠,有沒有疑問引润,模塊緩存在哪里?
Node.js 提供了 require.cache API 查看已緩存的模塊痒玩,返回值為對(duì)象椰拒,為了驗(yàn)證,這里做一個(gè)簡(jiǎn)單的測(cè)試凰荚,如下所示:
新建 test-module.js 文件
這里我導(dǎo)出一個(gè)變量和一個(gè)方法
module.exports={
a : 1,
test : () => { }
}
新建 test.js 文件
require('./test-module.js');
console.log(require.cache);
在這個(gè)文件里加載 test-module.js 文件,在之后打印下 require.cache 看下里面返回的是什么褒脯?看到以下結(jié)果應(yīng)該就很清晰了便瑟,模塊的文件名、地址番川、導(dǎo)出數(shù)據(jù)都很清楚到涂。
模塊循環(huán)引用
問題1
假設(shè)有 a.js、b.js 兩個(gè)模塊相互引用颁督,會(huì)有什么問題践啄?是否為陷入死循環(huán)?看以下例子
// a.js
console.log('a模塊start');
exports.test = 1;
undeclaredVariable = 'a模塊未聲明變量'
const b = require('./b');
console.log('a模塊加載完畢: b.test值:',b.test);
// b.js
console.log('b模塊start');
exports.test = 2;
const a = require('./a');
console.log('undeclaredVariable: ', undeclaredVariable);
console.log('b模塊加載完畢: a.test值:', a.test);
問題2
a 模塊中的 undeclaredVariable 變量在 b.js 中是否會(huì)被打映劣屿讽?
控制臺(tái)執(zhí)行 node a.js
,查看輸出結(jié)果:
a模塊start
b模塊start
undeclaredVariable: a模塊未聲明變量
b模塊加載完畢: a.test值: 1
a模塊加載完畢: b.test值: 2
問題1吠裆,啟動(dòng) a.js
的時(shí)候伐谈,會(huì)加載 b.js
,那么在 b.js
中又加載了 a.js
试疙,但是此時(shí) a.js
模塊還沒有執(zhí)行完诵棵,返回的是一個(gè) a.js
模塊的 exports
對(duì)象 未完成的副本
給到 b.js
模塊(因此是不會(huì)陷入死循環(huán)的)。然后 b.js
完成加載之后將 exports
對(duì)象提供給了 a.js
模塊
問題2祝旷,因?yàn)?undeclaredVariable
是一個(gè)未聲明的變量履澳,也就是一個(gè)掛在全局的變量嘶窄,那么在其他地方當(dāng)然是可以拿到的。
在執(zhí)行代碼之前距贷,Node.js 會(huì)使用一個(gè)代碼封裝器進(jìn)行封裝柄冲,例如下面所示:
(function(exports, require, module, __filename, __dirname) {
// 模塊的代碼
});
對(duì)象引用關(guān)系考察
也許是面試考察最多的問題:module.exports 與 exports 的區(qū)別?
exports 相當(dāng)于 module.exports 的快捷方式如下所示:
const exports = modules.exports;
但是要注意不能改變 exports 的指向储耐,我們可以通過 exports.test='a'
這樣來導(dǎo)出一個(gè)對(duì)象, 但是不能向下面示例直接賦值羊初,這樣會(huì)改變 exports 的指向
// 錯(cuò)誤的寫法 將會(huì)得到 undefined
exports = {
'a': 1,
'b': 2
}
// 正確的寫法
modules.exports = {
'a': 1,
'b': 2
}
更好的理解之間的關(guān)系,可以參考 JavaScript 中的對(duì)象引用 https://www.nodejs.red/#/javascript/object?
文章來源
轉(zhuǎn)載自 Nodejs技術(shù)棧