我們知道NodeJS遵循
CommonJS
的規範,使用require
關鍵字來載入模組,使用exports
和module.exports
來匯出模組,那麼這兩個匯出又有什麼關係或者區別呢?
其實,在node執行一個檔案時,會給這個檔案內生成一個 exports
物件和一個 module
物件,而這個module
物件又有一個屬性叫做 exports
新建一個index.js檔案 執行 node index.js
命令
console.log(exports)
console.log(module)
複製程式碼
可以看出控制檯的輸出結果如下:
我們再來看看exports
和 module.exports
有什麼關係呢?
我們在index.js 檔案中新增一句程式碼
console.log(exports === module.exports)
複製程式碼
會發現結果是 true
這說明,檔案開始執行的時候,它們是指向同一塊記憶體區域的
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
,此時的 exports
和 module.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
這個物件.
到了這裡,可能有人又會有疑問,為啥之前很多的模組都是需要引入才能使用,但是exports
和module.exports
我們沒有引用卻能直接使用?
這個問題的答案我們可以從Node的官方文件中找到答案,
(function(exports,require,module,__filename,__dirname){
...
})
複製程式碼
在進行了頭尾封裝後,各模組之間進行了作用域的隔離,避免了汙染全域性變數,通過頭尾封裝,實現了
- 保持頂層變數(用 var、 const 或 let 定義)作用在模組範圍內,而不是全域性物件。
- 提供一些看似全域性的但實際上是模組特定的變數
- 實現了從模組中匯出值的 module 和 exports 物件
- 包含模組絕對檔名(__filename)和目錄路徑(__dirname)的快捷變數
總結:
exports
物件是module
物件的一個屬性,在初始時module.exports
和exports
指向同一塊記憶體區域- 模組匯出的是
module.exports
,exports
只是對它的引用,在不改變exports
記憶體的情況下,修改exports
的值可以改變module.exports
的值 - 匯出時儘量使用
module.exports
,以免因為各種賦值導致的混亂