其他章節請看:
模組(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() 方法載入第三方模組有這麼一套規則:
- 找到執行檔案所屬目錄的 node_modules 資料夾
- 找到 node_modules/第三方模組名/package.json
- 找到 main 欄位指向的入口檔案
- 載入入口檔案
- 找不到 package.json 或找不到 main 欄位,又或者找不到 main 指向的檔案,就載入 index.js
- 以上都失敗,則去上一級找 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 中的變數。這就是外部訪問不了內部。
其他章節請看: