require時,exports和module.exports的區別你真的懂嗎?

面試會問

require 的運行機制和緩存策略你瞭解嗎?

require 加載模塊的是同步還是異步?談談你的理解

exports 和 module.exports 的區別是什麼?

require 加載模塊的時候加載的究竟是什麼?

require

提到 exports 和 module.exports 我們不得不提到 require 關鍵字。大家都知道 Node.js 遵循 CommonJS 規範,使用 require 關鍵字來加載模塊。

require 重複引入問題

問題:不知道小夥伴們在使用 require 引入模塊的時候有沒有相關,多個代碼文件中多次引入相同的模塊會不會造成重複呢?

因爲在 C++ 中通常使用#IFNDEF等關鍵字來避免文件的重複引入,但是在 Node.js 中無需關心這一點,因爲 Node.js 默認先從緩存中加載模塊,一個模塊被加載一次之後,就會在緩存中維持一個副本,如果遇到重複加載的模塊會直接提取緩存中的副本,也就是說在任何時候每個模塊都只在緩存中有一個實例。

require 加載模塊的時候是同步還是異步?

先回答問題,同步的!
但是面試官要是問你爲什麼是同步還是異步的呢?
其實這個答案並不是固定的,但是小夥伴們可以通過這幾方面給面試官解釋。

  1. 一個作爲公共依賴的模塊,當然想一次加載出來,同步更好
  2. 模塊的個數往往是有限的,而且 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 新手踩坑。

加入我們一起學習吧!

16b8a3d23a52b7d0?w=940&h=400&f=jpeg&s=217901
node學習交流羣

交流羣滿100人不能自動進羣, 請添加羣助手微信號:【coder_qi】備註node,自動拉你入羣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章