在上一篇文章中,我們發現使用 Node.js 的內建模組 http
之前需要先透過 require 關鍵字把模組帶入到 script 裡面,但還沒有好好理解模組到底是什麼東西。因此這篇學習筆記會比較詳細記錄模組 (module) 以及其中最常用的兩個屬性:exports
和 require
。
在 Node.js 的世界中,模組是個特別的物件 (對 JavaScript 學習者來說,這句話往往帶來雞皮疙瘩......)。module
物件會出現在每個 JavaScript 檔案,代表當前的模組。Node.js 當中的每個 JavaScript 檔案都被視為一個單獨的模組,而 module
物件則有助於我們和模組互動。
可以把模組想像成拼圖,當專案程式碼持續膨脹,透過模組化來將程式碼拆分成多個模組,對於管理、協作、debug 都非常有幫助。
在 Node REPL 把 module
列印出來,果然是個物件沒錯,裡面有好幾個 property 可供使用。接下來我們要關注 exports
那個空物件。
現在我們建立了一個檔案 math.js。
const add = (x, y) => x + y;
const PI = 3.14159;
const multiply = (x) => x * x;
以及另一個檔案 app.js。我們希望和上一篇學習筆記中使用 http
模組一樣,將 math.js 加入到 app.js,然後把 module
物件列印出來。由於這次是 require 電腦中的檔案,所以前面記得加上 ./
代表當前的資料夾位置。
const math = require("./math");
console.log(math);
列印結果出來了,是個......空陣列!
怎麼會這樣?這個空物件是何方神聖?其實它就是我們上面談到的 exports
物件,是 module
的其中一個 property。 exports
物件預設為空白,因為我們根本沒有把內容塞進去啊。現在回到 math.js,我們把函示集結在一個 math 物件中,賦值給 module.exports
。
const add = (x, y) => x + y;
const PI = 3.14159;
const multiply = (x) => x * x;
const math = {
add,
PI,
multiply,
};
module.exports = math;
再次觀察一下結果,math 物件成功被塞到 exports
物件,而且可以在 app.js 使用囉。由於函式都包在 math 物件中,所以使用時記得用點記法,像是 math.PI
。
Node.js 提供了更簡潔的 exports
語法,效果和 module.exports
大同小異。exports
本身指向 module.exports
,所以要注意一件事情:
重新賦值exports
之後,它就不會綁定到module.exports
了!
我們在 math.js 將 exports 隨便賦值一個字串,terminal 那邊馬上跳錯。
const add = (x, y) => x + y;
const PI = 3.14159;
const multiply = (x) => x * x;
exports = "My cat is adorable!"; // 將 exports 隨便賦值一個字串
exports.add = add;
exports.PI = PI;
exports.multuply = multiply;
require
是 Node.js 的全域變數,以用處來說,require
比較像指令,負責將其他模組帶入到 script 裡面;相較起來,module
變數則像是整理區,負責整理 require
帶進來的模組。
我們之前都只有用 require
帶入檔案,但其實資料夾也是可以的。事實上,在實際的開發專案中,帶入資料夾的作法可能還更常見。Node.js 有個特別的設定,只要資料夾當中含有 index.js,Node.js 就會自動偵查到。
舉例來說:require('./specifiedFolder')
,這樣對象即為 specifiedFolder 當中的 index.js 檔案。我們也可以將檔案名稱附上去,不加上 .js
也沒關係,Node.js 會自動先以 .js
檔案為偵查目標:require('./specifiedFolder/index')
。
其實 require 全域變數還有一些值得探討的內容,但由於課程的進度壓力,只能先留到之後有時間再好好去看一下了。另外不得不說,Node.js 的官方文件排版有夠讓人崩潰,到底為什麼要把一堆東西都擠在同一頁啦 🥹