其他章節請看:
模塊(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 中的變量。這就是外部訪問不了內部。
其他章節請看: