面試會問
require 的運行機制和緩存策略你瞭解嗎?require 加載模塊的是同步還是異步?談談你的理解
exports 和 module.exports 的區別是什麼?
require 加載模塊的時候加載的究竟是什麼?
require
提到 exports 和 module.exports 我們不得不提到 require 關鍵字。大家都知道 Node.js 遵循 CommonJS 規範,使用 require 關鍵字來加載模塊。
require 重複引入問題
問題:不知道小夥伴們在使用 require 引入模塊的時候有沒有相關,多個代碼文件中多次引入相同的模塊會不會造成重複呢?
因爲在 C++ 中通常使用#IFNDEF等關鍵字來避免文件的重複引入,但是在 Node.js 中無需關心這一點,因爲 Node.js 默認先從緩存中加載模塊,一個模塊被加載一次之後,就會在緩存中維持一個副本,如果遇到重複加載的模塊會直接提取緩存中的副本,也就是說在任何時候每個模塊都只在緩存中有一個實例。
require 加載模塊的時候是同步還是異步?
先回答問題,同步的!
但是面試官要是問你爲什麼是同步還是異步的呢?
其實這個答案並不是固定的,但是小夥伴們可以通過這幾方面給面試官解釋。
- 一個作爲公共依賴的模塊,當然想一次加載出來,同步更好
- 模塊的個數往往是有限的,而且 Node.js 在 require 的時候會自動緩存已經加載的模塊,再加上訪問的都是本地文件,產生的IO開銷幾乎可以忽略。
require() 的緩存策略
Node.js 會自動緩存經過 require 引入的文件,使得下次再引入不需要經過文件系統而是直接從緩存中讀取。不過這種緩存方式是經過文件路徑定位的,即使兩個完全相同的文件,但是位於不同的路徑下,會在緩存中維持兩份。
可以通過
console.log(require.cache)
獲取目前在緩存中的所有文件。
exports 與 module.exports 區別
js文件啓動時
在一個 node 執行一個文件時,會給這個文件內生成一個 exports 和 module 對象,
而module又有一個 exports 屬性。他們之間的關係如下圖,都指向一塊{}內存區域。
exports = module.exports = {};
看一張圖理解這裏更清楚:
require()加載模塊
require()加載模塊的時候我們來看一段實例代碼
//koala.js
let a = '程序員成長指北';
console.log(module.exports); //能打印出結果爲:{}
console.log(exports); //能打印出結果爲:{}
exports.a = '程序員成長指北哦哦'; //這裏辛苦勞作幫 module.exports 的內容給改成 {a : '程序員成長指北哦哦'}
exports = '指向其他內存區'; //這裏把exports的指向指走
//test.js
const a = require('/koala');
console.log(a) // 打印爲 {a : '程序員成長指北哦哦'}
看上面代碼的打印結果,應該能得到這樣的結論:
require導出的內容是module.exports的指向的內存塊內容,並不是exports的。
簡而言之,區分他們之間的區別就是 exports 只是 module.exports的引用,輔助後者添加內容用的。用內存指向的方式更好理解。
官網中的一個例子
看一下官方文檔中exports的應用
我們經常看到這樣的寫法:
exports = module.exports = somethings
上面的代碼等價於:
module.exports = somethings
exports = module.exports
原理很簡單,即 module.exports 指向新的對象時,exports 斷開了與 module.exports 的引用,那麼通過 exports = module.exports 讓 exports 重新指向 module.exports 即可。
使用的一點建議
建議:在使用的時候更建議大家使用module.exports(根據下面的例子也能得出)
Node.js 認爲每個文件都是一個獨立的模塊。如果你的包有兩個文件,假設是“a.js” 和“b.js”,然後“b.js” 要使用“a.js” 的功能,“a.js” 必須要通過給 exports 對象增加屬性來暴露這些功能:
// a.js
exports.verifyPassword = function(user, password, done) { ... }
完成這步後,所有需要“a.js” 的都會獲得一個帶有“verifyPassword” 函數屬性的對象:
// b.js
require(‘a.js’) // { verifyPassword: function(user, password, done) { ... } }
然而,如果我們想直接暴露這個函數,而不是讓它作爲某些對象的屬性呢?我們可以覆寫 exports 來達到目的,但是我們絕對不能把它當做一個全局變量:
// a.js
module.exports = function(user, password, done) { ... }
注意到我們是把“exports” 當做 module 對象的一個屬性。“module.exports” 和“exports” 這之間區別是很重要的,而且經常會使 Node.js 新手踩坑。
加入我們一起學習吧!
node學習交流羣
交流羣滿100人不能自動進羣, 請添加羣助手微信號:【coder_qi】備註node,自動拉你入羣。