在 Node.js 中,若有載入多支 JS 檔需求時,並不能像以往前端開發一樣,用 <script>
標籤引入多支檔案,且它也沒有 HTML
檔案來支持你這麼做。Node.js 須要使用 require
的方式來載入其他的 JS 檔案,這也是模組系統的主要使用方法。
什麼是模組?
在 Node.js 中的模組(Module),指的就是單一支一支的 JS 檔案,透過互相引入的方式來達成繫節關係。
簡單範例
資料夾結構:
nodejs-module
∟ A.js
∟ B.js
1 | // A.js |
1 | // B.js |
執行 node A.js
1 | A.js 開始執行了! |
也就是說,當你執行某支檔案時,程式碼執行到 require
字眼時,Node.js 就會執行被 require 那支檔案內的程式碼。
在 Node.js 中沒有全域作用域,只有模組作用域
資料夾結構:
nodejs-module
∟ A.js
∟ B.js
1 | // A.js |
1 | // B.js |
在這個例子當中,若是以以往 <script>
載入的概念來看是像這樣的
1 | <script src="A.js"></script> |
全域的 foo
變數會被後來載入的 B.js
內的 foo 覆蓋,預期應 foo 應該會是 B。但是在 Node.js 中是這樣嗎?
試著來輸出結果看看
1 | A.js 開始執行 |
延續上面的例子,如果我們在 A.js
中定義一個函數,並試著在 B.js
呼叫這個函數,能夠成功嗎?
1 | // A.js |
1 | // B.js |
輸出結果
1 | console.log(add(10, 5)) |
由上面的例子我們可以理解所謂模組作用域其實就是
檔案作用域
,只要不是寫在自己本身的變數或函式,即使我載入了你,我還是不能取用你的東西的,載入就是單純執行對方內部的程式碼而已。
注意事項
require 時副檔名可以省略
1
require('./B.js') // === require('./B')
載入自己寫的模組(JS檔),相對路徑不得省略
1
require('B.js') // Cannot find module 'B'
模組間的通信
上面的例子或許讓你感到很困惑,既然載入了它,不就是為了要取用它的部分成員嗎?如果只是執行內部的程式碼,而不能取用成員,為何還要載入它呢?
是的,Node.js 中預設模組是封閉了,你可以載入我,但你取用不到我任何的成員,內部無法取用外部; 外部也無法取用內部。
但有時候載入模組的目的不只是單純為了執行裡面的程式碼,更重要的是取用裡面的成員阿!
那,該怎麼做?
導出 (exports)
Node 中的 require
方法有兩個作用
- 載入模組並執行裡面的程式碼 (也就是上面的例子)
- 拿到被加載模組所導出的
接口物件
- 在每個模組中都提供一個物件
exports
,我們可以將它導出,並讓其他模組接收,而它預設是一個空的物件
- 在每個模組中都提供一個物件
資料夾結構:
nodejs-module
∟ A.js
∟ B.js
1 | // A.js |
1 | // B.js |
執行 node A.js
結果
1 | B.js 被加載執行了 |
A.js
利用一個變數 require 了 B.js 的接口物件。結果發生了兩件事
- 執行了 B.js 檔案內的程式碼
result
變數接收了 B.js 的接口物件,由於 B.js 沒有導出任何成員,因此 A.js 接到的是空物件。
你可以試著在任一模組裡,印出 exports
物件,證明它預設就是一個空物件
1 | // B.js |
執行 node B
,果然得到一個空物件
1 | {} |
導出成員
了解上面的原理後,接著我們可以開始導出成員了,只需要將想被導出的成員放入 exports
物件中,接著讓須要取用的模組 require
即可
1 | // B.js |
1 | // A.js |
輸出 node A.js
結果
1 | B |