一文搞懂exports和module.exports的關係和區別

閃現A小兵發表於2020-03-02

我們知道NodeJS遵循 CommonJS 的規範,使用 require 關鍵字來載入模組,使用 exportsmodule.exports 來匯出模組,那麼這兩個匯出又有什麼關係或者區別呢?

其實,在node執行一個檔案時,會給這個檔案內生成一個 exports 物件和一個 module 物件,而這個module 物件又有一個屬性叫做 exports

新建一個index.js檔案 執行 node index.js 命令

console.log(exports)
console.log(module)
複製程式碼

可以看出控制檯的輸出結果如下:

在這裡插入圖片描述
我們再來看看 exportsmodule.exports 有什麼關係呢? 我們在index.js 檔案中新增一句程式碼

console.log(exports === module.exports)
複製程式碼

會發現結果是 true 這說明,檔案開始執行的時候,它們是指向同一塊記憶體區域的

一文搞懂exports和module.exports的關係和區別
當檔案執行完畢的時候,只有module.exports 變數被返回了,以便後續被其他模組 require 引用,為了證明這個觀點,我們可以新建一個檔案 index2.js 進行測試 index2.js

exports.a = 1
複製程式碼

然後在 index3.js 中引用

const module2 = require('./index2')
console.log(module2)
複製程式碼

控制檯輸出: { a: 1 } 然後我們在 index2.js 中新增程式碼:

exports.a = 1
module.exports = {
  b:2
}
複製程式碼

在這裡同時使用兩個匯出方法,檢視控制檯輸出結果為 { b: 2 } 此時,我們繼續在 index2.js 檔案中新增

console.log(exports === module.exports)
複製程式碼

結果為false,此時的 exportsmodule.exports 已經不是指向同一塊記憶體地址了,因為前面的程式碼裡面,我們使用了

module.exports = {
  b:2
}
複製程式碼

這導致了 module.exports重新指向了新的記憶體地址, 但是當我們執行 node index3.js 檢視index3.js 的執行結果時,看到的是 {b:2} 而不是 {a:1} 證明了我們上面的觀點: 只有module.exports 變數被返回了 因此,初始化的狀態,我們可以用如下程式碼來幫助理解:

var module = {
	exports:{}
}
var exports = module.exports
複製程式碼

而最終的匯出結果是 module.exports 這個物件. 到了這裡,可能有人又會有疑問,為啥之前很多的模組都是需要引入才能使用,但是exportsmodule.exports 我們沒有引用卻能直接使用? 這個問題的答案我們可以從Node的官方文件中找到答案,

在這裡插入圖片描述
Node API 傳送門: module wrapper 這裡,Node的官方文件裡面提到, NodeJS 應用在檔案被執行前會被包裝一層:

(function(exports,require,module,__filename,__dirname){
  ...
})
複製程式碼

在進行了頭尾封裝後,各模組之間進行了作用域的隔離,避免了汙染全域性變數,通過頭尾封裝,實現了

  • 保持頂層變數(用 var、 const 或 let 定義)作用在模組範圍內,而不是全域性物件。
  • 提供一些看似全域性的但實際上是模組特定的變數
    • 實現了從模組中匯出值的 module 和 exports 物件
    • 包含模組絕對檔名(__filename)和目錄路徑(__dirname)的快捷變數

總結:

  1. exports 物件是 module 物件的一個屬性,在初始時 module.exportsexports 指向同一塊記憶體區域
  2. 模組匯出的是 module.exports , exports 只是對它的引用,在不改變exports 記憶體的情況下,修改exports 的值可以改變 module.exports 的值
  3. 匯出時儘量使用 module.exports ,以免因為各種賦值導致的混亂

相關文章