前端學習 node 快速入門 系列 —— 模組(module)
阿新 • • 發佈:2021-03-11
>其他章節請看:
>
>[前端學習 node 快速入門 系列][1]
## 模組(module)
### 模組的匯入
#### 核心模組
在 [初步認識 node][初步認識 node] 這篇文章中,我們在讀檔案的例子中用到了 `require('fs')`,在寫最簡單的伺服器的例子中用到了 `require('http')`,除了 fs 和 http,node 提供了很多核心模組,例如:path(路徑)、os(作業系統)、events(事件)、url 等等。
如果我們需要使用**核心模組**的功能,就使用 `require(模組名)` 方法進行引入。
#### 第三方模組
在 [npm][npm] 一文中,我們知道了如何用 npm 下載包。如果我們需要使用**第三方的模組**,也可以像引入核心模組那樣。請看示例:
```javascript
// 首先得下載包。後續不再提醒
$ npm install underscore
let _ = require('underscore')
console.log(_.VERSION)
console.log(typeof _.clone)
console.log(typeof _.find)
/*
輸出:
1.12.0
function
function
*/
```
*注*:由於核心模組和第三方模組(npm 下載的包)都是通過**模組名**載入,所以它們的名字不會相同。也就是說 node 不允許第三方模組佔用 node 核心模組的名字。
require() 方法載入第三方模組有這麼一套**規則**:
1. 找到執行檔案所屬目錄的 node_modules 資料夾
1. 找到 node_modules/第三方模組名/package.json
1. 找到 main 欄位指向的入口檔案
- 載入入口檔案
1. 找不到 package.json 或找不到 main 欄位,又或者找不到 main 指向的檔案,就載入 index.js
1. 以上都失敗,則去上一級找 node_modules 目錄,找不到又去上一級,上一級,直到根目錄都沒有找到,就報錯處理
我們用 underscore 這個第三方模組驗證一下上面的規則。請看示例:
首先準備環境,匯入 underscore。
```javascript
// 進入專案(D:\實驗樓\node-study\test2)
// 快速生成 package.json
$ npm init -y
// 下載 underscore
$ npm install underscore
// 建立 test2/index.js
let _ = require('underscore')
console.log(_.VERSION)
// 執行
$ node index
1.12.0
function
```
現在模組已經正常匯入。
我們首先驗證一下:載入的是否是 main 欄位指向的檔案。具體步驟請看:
```javascript
// 修改 package.json 中的 main 欄位。目錄是:test2\node_modules\underscore\package.json
{
"main": "underscore.js",
// 改為
"main": "a.js",
}
// 新建 a.js 檔案。目錄是:test2\node_modules\underscore\a.js
exports.name = 'ph'
// 修改 index.js 內容如下
let _ = require('underscore')
console.log(_)
$ node index
{ name: 'ph' }
```
重置 package.json 中 main 欄位,將 underscore 模組的主入口檔案改為 a.js,最終我們拿到的 underscore 確實是我們返回的 `{ name: 'ph' }`。
接著將 a.js 改為 index.js,執行 `node index`,輸出的還是 `{ name: 'ph' }`。說明 require() 找不到 main 指向的入口檔案 a.js,於是就去找 index.js。
最後將 node_modules 資料夾剪下至 D 盤根目錄中,執行 `node index` 仍舊輸出 `{ name: 'ph' }`。
#### 自定義模組
在 node 中,每個檔案都被視為一個**單獨的模組**。
瀏覽器可以通過 script 標籤執行多個 js 檔案,但 node 只能執行**一個檔案**,不過我們可以通過 require() 方法載入其他 js 檔案,js 檔案又載入其他 js 檔案,如此迴圈,最終就形成了一個大大的模組。請看示例:
```javascript
// index.js 內容
let m = require('./a') // {1}
console.log(m.sum(1,2))
// a.js 內容
let sum = (v1, v2) => v1 + v2
module.exports.sum = sum
// 執行
$ node index
3
```
index.js 中載入了 a.js。相當於運行了 2 個檔案。
如果將 `require('./a')` 改成 `require('a')`(行{1}),執行則會報錯:Error: Cannot find module 'a'。因為 `require('a')` 會直接去核心模組和第三方模組中找,結果又沒有找到對應的 a 模組。只有傳入 require(id) 方法中的 id 以 **'/'、'./'或'../'開頭**,才會從自定義模組中找。
通常 '/' 很少會用到。請看示例:
```javascript
// D:\1.js
console.log('hello')
// D:\實驗樓\node-study\index.js
require('/1')
$ node index
hello
```
執行 `require('/1')` 會到檔案的根目錄(D 盤)中找 1.js。如果別人用你的專案,還得在根目錄下也存一份同樣的 1.js 檔案嗎?這是很困難的。
到現在,我們已經知道 require() 方法能匯入模組,也能執行模組。其實它還有一個**重要特性**:優先從快取中載入,重複匯入也只會執行一次。
```javascript
// 1.js
var a = 1
let two = require('./2')
let twoCopy = require('./2')
console.log(a)
console.log(`two a = ${two.a}`)
console.log(`twoCopy a = ${twoCopy.a}`)
// 2.js
var a = 2
console.log(`我是 2.js`) // {1}
exports.a = a;
// 執行
$ node 1
我是 2.js
1
two a = 2
twoCopy a = 2
```
在 1.js 中匯入了兩次 2.js,但只輸出了一次 `我是 2.js`(行{1})。
請接著看:
```javascript
// index.js
require('./a')
require('./b')
// a.js
require('./b')
// b.js
console.log('hello')
$ node index
hello
```
模組 b.js 同樣被匯入了兩次,但也只執行了一次。
### 模組的匯出
每個檔案都有一個 **module** 的變數。就像 require() 方法,無需載入就可以直接使用。請看示例:
```javascript
// index.js
console.log(typeof require)
console.log(module)
// 執行
$ node index
function
Module {
id: '.',
...
exports: {}, // {1}
...
}
```
我們看到 module 裡面有一個 **exports** 的物件(行{1})。
如果我們的模組需要對外提供介面,就可以使用 **module.exports**。請看示例:
```javascript
// index.js
let m = require('./1')
console.log(`name = ${m.name} age = ${m.age}`)
// 1.js
let name = 'ph'
let age = 'age'
module.exports.name = name // {1}
$ node index
name = ph age = undefined
```
模組 1.js 只匯出了 name 屬性,所以 index.js 只能讀取到 name ,而讀不到 age。
module.exports 還提供了一個**快捷方式**:直接使用 exports。例如將 `module.exports.name = name`(行{1}) 改成 `exports.name = name` 效果也是一樣的。
*注*:由於 exports 是 module.exports 的引用,就像任何變數一樣,如果將新值分配給 exports,則它不再繫結到 module.exports。請看示例:
```javascript
// index.js
let m = require('./1')
console.log(m)
// 1.js
module.exports = 'ph' // {1}
$ node index
ph
```
如果將 `module.exports = 'ph'`(行{1}) 換成 `exports = 'ph'`,輸出結果是:`{}`,說明匯出失敗。
### 模組的作用域
node 中沒有全域性作用域,只有**模組作用域**。內部訪問不了外部,外部訪問不了內部。請看示例:
```javascript
// 1.js
var a = 1
var a = 2
console.log(a)
```
執行 `$ node 1` 輸出 2。稍微改一下:
```javascript
// 1.js
var a = 1
require('./2')
console.log(a)
// 2.js
var a = 2
```
再次執行 `$ node 1`,這次輸出的是 1。說明 2.js 中的 變數 `a` 沒能影響到 1.js 中的變數 `a`。繼續:
```javascript
// 1.js
require('./2')
console.log(a)
// 2.js
var a = 1
```
再次執行 `$ node 1`,輸出報錯資訊 `ReferenceError: a is not defined`,說明 1.js 不能訪問 2.js 中的變數。這就是外部訪問不了內部。
>其他章節請看:
>
>[前端學習 node 快速入門 系列][1]
[1]: https://www.cnblogs.com/pengjiali/p/14494587.html '前端學習 node 快速入門 系列'
[初步認識 node]: https://www.cnblogs.com/pengjiali/p/14496123.html '初步認識 node'
[npm]: https://www.cnblogs.com/pengjiali/p/14509928.ht