前端學習 node 快速入門 系列 —— 模組(module)

彭加李發表於2021-03-11

其他章節請看:

前端學習 node 快速入門 系列

模組(module)

模組的匯入

核心模組

初步認識 node 這篇文章中,我們在讀檔案的例子中用到了 require('fs'),在寫最簡單的伺服器的例子中用到了 require('http'),除了 fs 和 http,node 提供了很多核心模組,例如:path(路徑)、os(作業系統)、events(事件)、url 等等。

如果我們需要使用核心模組的功能,就使用 require(模組名) 方法進行引入。

第三方模組

npm 一文中,我們知道了如何用 npm 下載包。如果我們需要使用第三方的模組,也可以像引入核心模組那樣。請看示例:

// 首先得下載包。後續不再提醒
$ 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 資料夾
  2. 找到 node_modules/第三方模組名/package.json
  3. 找到 main 欄位指向的入口檔案
    • 載入入口檔案
  4. 找不到 package.json 或找不到 main 欄位,又或者找不到 main 指向的檔案,就載入 index.js
  5. 以上都失敗,則去上一級找 node_modules 目錄,找不到又去上一級,上一級,直到根目錄都沒有找到,就報錯處理

我們用 underscore 這個第三方模組驗證一下上面的規則。請看示例:

首先準備環境,匯入 underscore。

// 進入專案(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 欄位指向的檔案。具體步驟請看:

// 修改 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 檔案,如此迴圈,最終就形成了一個大大的模組。請看示例:

// 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 以 '/'、'./'或'../'開頭,才會從自定義模組中找。

通常 '/' 很少會用到。請看示例:

// D:\1.js
console.log('hello')

// D:\實驗樓\node-study\index.js
require('/1') 

$ node index
hello

執行 require('/1') 會到檔案的根目錄(D 盤)中找 1.js。如果別人用你的專案,還得在根目錄下也存一份同樣的 1.js 檔案嗎?這是很困難的。

到現在,我們已經知道 require() 方法能匯入模組,也能執行模組。其實它還有一個重要特性:優先從快取中載入,重複匯入也只會執行一次。

// 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})。

請接著看:

// index.js
require('./a')
require('./b')

// a.js
require('./b')

// b.js
console.log('hello')

$ node index
hello

模組 b.js 同樣被匯入了兩次,但也只執行了一次。

模組的匯出

每個檔案都有一個 module 的變數。就像 require() 方法,無需載入就可以直接使用。請看示例:

// index.js
console.log(typeof require)
console.log(module)

// 執行
$ node index
function
Module {
  id: '.',
  ...
  exports: {}, // {1}
  ...
}

我們看到 module 裡面有一個 exports 的物件(行{1})。

如果我們的模組需要對外提供介面,就可以使用 module.exports。請看示例:

// 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。請看示例:

// 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 中沒有全域性作用域,只有模組作用域。內部訪問不了外部,外部訪問不了內部。請看示例:

// 1.js
var a = 1
var a = 2
console.log(a)

執行 $ node 1 輸出 2。稍微改一下:

// 1.js
var a = 1
require('./2')
console.log(a)

// 2.js
var a = 2

再次執行 $ node 1,這次輸出的是 1。說明 2.js 中的 變數 a 沒能影響到 1.js 中的變數 a。繼續:

// 1.js
require('./2')
console.log(a)

// 2.js
var a = 1

再次執行 $ node 1,輸出報錯資訊 ReferenceError: a is not defined,說明 1.js 不能訪問 2.js 中的變數。這就是外部訪問不了內部。

其他章節請看:

前端學習 node 快速入門 系列

相關文章