Node.js 模塊機制
Node.js 模塊機制采用了 Commonjs 規(guī)范测暗,彌補了當前 JavaScript 開發(fā)大型應用沒有標準的缺陷诗芜,類似于 Java 中的類文件倦逐,Python 中的 import 機制寝姿,Node.js 中可以通過 module.exports、require 來導出和引入一個模塊.
在模塊加載機制中乙帮,Node.js 采用了延遲加載的策略,只有在用到的情況下极景,系統(tǒng)模塊才會被加載察净,加載完成后會放到 binding_cache 中。
面試指南
require的加載機制盼樟?
氢卡,參考:模塊加載機制module.exports與exports的區(qū)別
,參考:對象引用關系考察假設有a.js晨缴、b.js兩個模塊相互引用译秦,會有什么問題?是否為陷入死循環(huán)击碗?
筑悴,參考正文“模塊循環(huán)引用問題1”a模塊中的undeclaredVariable變量在b.js中是否會被打印延都?
雷猪,參考正文“模塊循環(huán)引用問題2”模塊在require的過程中是同步還是異步?
晰房,參考正文模塊加載機制 “文件模塊“
模塊的分類
系統(tǒng)模塊
C/C++ 模塊求摇,也叫 built-in 內建模塊,一般用于 native 模塊調用殊者,在 require 出去
native 模塊与境,在開發(fā)中使用的 Node.js 的 http、buffer猖吴、fs 等摔刁,底層也是調用的內建模塊 (C/C++)。
第三方模塊
非 Node.js 自帶的模塊稱為第三方模塊海蔽,其實還分為路徑形式的文件模塊(以 .
共屈、 ..
绑谣、 /
開頭的)和自定義的模塊(比如 express、koa 框架拗引、moment.js 等)
javaScript 模塊:例如
hello.js
json 模塊:例如
hello.json
C/C++ 模塊:編譯之后擴展名為 .node 的模塊借宵,例如
hello.node
目錄結構
<pre>├── benchmark 一些 Node.js 性能測試代碼
├── deps Node.js 依賴
├── doc 文檔
├── lib Node.js 對外暴露的 js 模塊源碼
├── src Node.js 的 c/c++ 源碼文件,內建模塊
├── test 單元測試
├── tools 編譯時用到的工具
├── doc api 文檔
├── vcbuild.bat win 平臺 makefile 文件
├── node.gyp node-gyp 構建編譯任務的配置文件
...
</pre>
模塊加載機制
面試中可能會問到能說下require的加載機制嗎?
在 Node.js 中模塊加載一般會經歷 3 個步驟矾削, 路徑分析
壤玫、 文件定位
、 編譯執(zhí)行
哼凯。
按照模塊的分類欲间,按照以下順序進行優(yōu)先加載:
系統(tǒng)緩存:模塊被執(zhí)行之后會會進行緩存,首先是先進行緩存加載断部,判斷緩存中是否有值猎贴。
系統(tǒng)模塊:也就是原生模塊,這個優(yōu)先級僅次于緩存加載家坎,部分核心模塊已經被編譯成二進制嘱能,省略了
路徑分析
、文件定位
虱疏,直接加載到了內存中惹骂,系統(tǒng)模塊定義在 Node.js 源碼的 lib 目錄下,可以去查看做瞪。文件模塊:優(yōu)先加載
.
对粪、..
、/
開頭的装蓬,如果文件沒有加上擴展名著拭,會依次按照.js
、.json
牍帚、.node
進行擴展名補足嘗試儡遮,那么在嘗試的過程中也是以同步阻塞模式來判斷文件是否存在,從性能優(yōu)化的角度來看待暗赶,.json
鄙币、.node
最好還是加上文件的擴展名。目錄做為模塊:這種情況發(fā)生在文件模塊加載過程中蹂随,也沒有找到十嘿,但是發(fā)現(xiàn)是一個目錄的情況,這個時候會將這個目錄當作一個
包
來處理岳锁,Node 這塊采用了 Commonjs 規(guī)范绩衷,先會在項目根目錄查找 package.json 文件,取出文件中定義的 main 屬性("main":"lib/hello.js")
描述的入口文件進行加載,也沒加載到咳燕,則會拋出默認錯誤: Error: Cannot find module 'lib/hello.js'node_modules 目錄加載:對于系統(tǒng)模塊勿决、路徑文件模塊都找不到,Node.js 會從當前模塊的父目錄進行查找招盲,直到系統(tǒng)的根目錄
require 模塊加載時序圖
模塊緩存在哪
上面講解了模塊的加載機制剥险,中間有提到模塊初次加載之后會緩存起來,有沒有疑問宪肖,模塊緩存在哪里?
Node.js 提供了 require.cache API 查看已緩存的模塊健爬,返回值為對象控乾,為了驗證,這里做一個簡單的測試娜遵,如下所示:
新建 test-module.js 文件
這里我導出一個變量和一個方法
module.exports={
a : 1,
test : () => { }
}
新建 test.js 文件
require('./test-module.js');
console.log(require.cache);
在這個文件里加載 test-module.js 文件蜕衡,在之后打印下 require.cache 看下里面返回的是什么?看到以下結果應該就很清晰了设拟,模塊的文件名慨仿、地址、導出數(shù)據(jù)都很清楚纳胧。
模塊循環(huán)引用
問題1
假設有 a.js镰吆、b.js 兩個模塊相互引用,會有什么問題跑慕?是否為陷入死循環(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 中是否會被打印核行?
控制臺執(zhí)行 node a.js
牢硅,查看輸出結果:
a模塊start
b模塊start
undeclaredVariable: a模塊未聲明變量
b模塊加載完畢: a.test值: 1
a模塊加載完畢: b.test值: 2
問題1,啟動 a.js
的時候芝雪,會加載 b.js
减余,那么在 b.js
中又加載了 a.js
,但是此時 a.js
模塊還沒有執(zhí)行完惩系,返回的是一個 a.js
模塊的 exports
對象 未完成的副本
給到 b.js
模塊(因此是不會陷入死循環(huán)的)位岔。然后 b.js
完成加載之后將 exports
對象提供給了 a.js
模塊
問題2,因為 undeclaredVariable
是一個未聲明的變量蛆挫,也就是一個掛在全局的變量赃承,那么在其他地方當然是可以拿到的。
在執(zhí)行代碼之前悴侵,Node.js 會使用一個代碼封裝器進行封裝瞧剖,例如下面所示:
(function(exports, require, module, __filename, __dirname) {
// 模塊的代碼
});
對象引用關系考察
也許是面試考察最多的問題:module.exports 與 exports 的區(qū)別?
exports 相當于 module.exports 的快捷方式如下所示:
const exports = modules.exports;
但是要注意不能改變 exports 的指向,我們可以通過 exports.test='a'
這樣來導出一個對象, 但是不能向下面示例直接賦值抓于,這樣會改變 exports 的指向
// 錯誤的寫法 將會得到 undefined
exports = {
'a': 1,
'b': 2
}
// 正確的寫法
modules.exports = {
'a': 1,
'b': 2
}
更好的理解之間的關系做粤,可以參考 JavaScript 中的對象引用 https://www.nodejs.red/#/javascript/object
收錄時間: 2019/08/26