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

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

其實,在node執行一個文件時,會給這個文件內生成一個 exports 對象和一個 module 對象,而這個module 對象又有一個屬性叫作 exportsnode

新建一個index.js文件 執行 node index.js 命令api

console.log(exports)
console.log(module)
複製代碼

能夠看出控制檯的輸出結果以下: bash

在這裏插入圖片描述
咱們再來看看 exportsmodule.exports 有什麼關係呢? 咱們在index.js 文件中添加一句代碼

console.log(exports === module.exports)
複製代碼

會發現結果是 true 這說明,文件開始執行的時候,它們是指向同一塊內存區域的app

graph LR;
A[exports] ; B[內存:xxx]; C[module.exports]
    A-->B
    C-->B
複製代碼

當文件執行完畢的時候,只有module.exports 變量被返回了,以便後續被其餘模塊 require 引用,爲了證實這個觀點,咱們能夠新建一個文件 index2.js 進行測試 index2.js測試

exports.a = 1
複製代碼

而後在 index3.js 中引用ui

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

控制檯輸出: { a: 1 } 而後咱們在 index2.js 中添加代碼:spa

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

在這裏同時使用兩個導出方法,查看控制檯輸出結果爲 { b: 2 } 此時,咱們繼續在 index2.js 文件中添加code

console.log(exports === module.exports)
複製代碼

結果爲false,此時的 exportsmodule.exports 已經不是指向同一塊內存地址了,由於前面的代碼裏面,咱們使用了cdn

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 ,以避免由於各類賦值致使的混亂
相關文章
相關標籤/搜索