前言javascript
本系列文章是根據Mosh
大佬的視頻教程全方位Node開發 - Mosh整理而成,我的以爲視頻很是不錯,因此計劃邊學習邊整理成文章方便後期回顧。該視頻教程是英文的,可是有中文字幕,感謝marking1212提供的中文字幕翻譯。前端
經過本篇文章的學習,你將瞭解什麼是模塊,爲何須要模塊,以及它們是怎麼工做的。java
一般咱們會用console.log()
在控制檯打印一些東西,這個console
就是全局對象,它的做用域是全局,也就是說能夠在任何地方任何文件調用它。node
在Node
中也有不少全局對象。編程
在瀏覽器中,window
對象表明的是全局對象。瀏覽器
當咱們調用console.log()
的時候,實際上調用的是window.console.log()
,固然咱們能夠直接省略前面的window.
,JavaScript引擎會自動前置window
關鍵字。bash
相似的你看到的其餘函數也屬於全局對象,好比setTimeout()
、clearTimeout()
等,咱們能夠用window.setTimeout()
來調用,也能夠直接調用。app
一樣的,當咱們聲明一個變量ide
var message = ''
複製代碼
這個變量一樣屬於window
對象。模塊化
這裏要注意的是Node
中並無window
對象,它有個相似的對象global
,咱們一樣能夠經過global
調用setTimeout()
或者其餘函數,也能夠直接省略global.
調用。
還有一點要注意的是,這裏面定義的message
變量並不會添加到global
對象中,換句話說,好比你想打印console.log(global.message)
,你會在控制檯看到未定義的錯誤。
咱們在app.js
文件輸入如下代碼:
var message = ''
console.log(global.message) // undefined
複製代碼
在控制檯會打印出undefined
。
global
的做用域只在這個文件,也就是app.js
文件中,在文件外它是不可見的,這就是Node
的模塊化系統所致。
在客戶端,JavaScript
是運行在瀏覽器中。
當咱們定義一個函數或變量,它的做用域是全局的,好比咱們定義一個sayHello
函數
var sayHello = function (name) {
}
複製代碼
它的做用域是全局,能夠經過window
對象訪問,但其實這種行爲邏輯有個問題,在真實的編程中,咱們常常講不一樣的代碼放到不一樣的文件中,也許在兩個文件中定義了同名的sayHello
函數,由於函數被添加到全局變量,當咱們在另外一個文件中定義這個名字的函數,新的定義將會覆蓋舊的定義,這就是全局做用域的問題。
因此爲了創建可信和可維護的應用咱們應避免定義全局函數和變量,咱們應該模塊化。
咱們須要建立小型拼裝塊或者叫模塊來存放變量和函數,這樣不一樣模塊之間的同名函數和變量不會相互覆蓋,他們被封裝在模塊中了。
Node
的核心概念就是模塊。
每一個Node
中的文件都被看作模塊,每一個模塊中定義的變量和函數做用域僅在模塊內,以面向對象的觀點咱們叫它們私有成員,它們在容器外,也就是模塊外是不可見的。
若是你要在模塊外使用一個定義在模塊中的變量或函數,咱們須要明確的導出它爲公開成員。
每一個Node
工程至少要包含一個文件或者說一個模塊。
這裏的app.js
就是這個項目的主模塊,咱們把module
對象打印出來看一下
console.log(module)
複製代碼
這個module
對象看起來像全局對象,你可能會想經過全局對象global
訪問它,可是實際上它不是全局對象。
回到控制檯,咱們運行node app.js
,能夠看到打印出了module
對象的詳細信息,它是一個JSON對象,包含了鍵值對,好比id,每一個模塊都有獨一無二的id。
Module {
id: '.',
exports: {},
parent: null,
filename: 'F:\\2020\\study\\Node.js\\node-course\\first-app\\app.js',
loaded: false,
children: [],
paths:
[ 'F:\\2020\\study\\Node.js\\node-course\\first-app\\node_modules',
'F:\\2020\\study\\Node.js\\node-course\\node_modules',
'F:\\2020\\study\\Node.js\\node_modules',
'F:\\2020\\study\\node_modules',
'F:\\2020\\node_modules',
'F:\\node_modules' ] }
複製代碼
這裏,咱們先不用瞭解每一個屬性的意思,隨着課程的深刻咱們都會了解。
因此在Node
中,每一個文件都是模塊,模塊中定義的成員做用域只在模塊中,它們在模塊外是不可見的。
咱們給應用添加一個模塊,新建一個文件logger.js
,假設咱們爲記錄信息建立一個模塊,咱們要在不少地方複用這個模塊,有可能的話也會在別的應用複用。
在模塊中,咱們假設要使用一個遠程日誌服務來記錄咱們的日誌,有個其餘的網站能夠提供日誌服務,它提供了一個URL,能夠經過給它發送HTTP請求來記錄日誌。
咱們在logger.js
寫一些代碼
var url = 'http://mylogger.io/log' // 這不是一個真實的地址,只作演示用,咱們就假設這個例子會向這個地址發送請求
function log(message) {
console.log(message)
}
複製代碼
這個變量和這個函數的做用域都是這個文件,它們是私有的,在外部不可見。
可是,咱們想在app.js
也就是咱們的主模塊中想用到日誌模塊,咱們應該要訪問log
這個函數,咱們須要從app模塊調用它,咱們須要將它變爲公共的,能夠在外部訪問。
還記得module
對象嗎,裏面有個exports
屬性
Module {
id: '.',
exports: {},
parent: null,
filename: 'F:\\2020\\study\\Node.js\\node-course\\first-app\\app.js',
loaded: false,
children: [],
paths:
[ 'F:\\2020\\study\\Node.js\\node-course\\first-app\\node_modules',
'F:\\2020\\study\\Node.js\\node-course\\node_modules',
'F:\\2020\\study\\Node.js\\node_modules',
'F:\\2020\\study\\node_modules',
'F:\\2020\\node_modules',
'F:\\node_modules' ] }
複製代碼
咱們看到這個屬性是一個空對象,全部添加到這個對象的屬性將可在外部訪問。
回到咱們的logger.js
模塊,加入如下代碼
module.exports.log = log
複製代碼
咱們給exports
對象添加一個log
方法,讓它賦值爲咱們這裏的log
函數,換句話說,咱們這裏的導出的對象,有一個同名的log
方法,相似的咱們想公開這個url
變量能夠這樣作
module.exports.url = url
複製代碼
咱們也能夠在導出的時候更名,不必定要相同的名字,好比在內部咱們叫它url
,可是咱們在外部叫它endPoint
module.exports.endPoint = url
複製代碼
這個例子中,咱們不須要導出url
變量,由於這純粹是實現的細節,在現實中,每一個模塊都有不少變量和函數,咱們只想公開最少限度的成員,由於咱們想保持模塊簡單易用。
打個比方,想象DVD播放器,DVD播放器有幾個按鈕是能夠給咱們操做的,這些按鈕就是咱們所說的DVD播放器的公開接口。
可是在播放器內部有不少複雜到咱們不須要了解的元件,它們徹底能夠從一個換成另一個,但無論怎麼換它們提供給外部的按鈕都是固定的。
在logger
模塊中,這個url
是實現細節,其餘的模塊不須要了解它,它們只要調用log
函數就能夠了,因此咱們讓log
變爲公開的,而url
保持私有。
接下來,咱們要在app
模塊中使用它。
咱們使用require
函數來加載模塊,這是Node
纔有的函數,瀏覽器裏沒有,這個函數須要一個參數,也就是咱們想加載的模塊名稱。
require('./logger.js') // 這邊的.js能夠省略,由於Node知道這個是一個js文件,會自動添加擴展名
複製代碼
因爲app.js
和logger.js
在同一個文件夾下,咱們使用./
來表明當前文件夾。
若是這個模塊是在子文件夾中能夠添加子文件夾的路徑。
require('./subFolder/logger')
複製代碼
若是在父文件夾,可使用../
來表明相對路徑
require('../logger')
複製代碼
這個require
函數返回參數模塊導出的對象,就是這邊的exports
對象,咱們來代碼演示一下
app.js
var logger = require('./logger')
console.log(logger)
複製代碼
咱們把logger
變量打印出來看下獲得什麼,回到控制檯,執行node app.js
{ log: [Function: log] }
複製代碼
咱們獲得一個對象,對象中有一個單一的函數log
,咱們就能夠在app.js
中調用這個函數了。
咱們來調用一個看看
var logger = require('./logger')
logger.log('message')
複製代碼
回到控制檯,運行程序,能夠看到打印了message
信息。
這就是Node
中模塊的工做方式,定義一個模塊,導出一個或多個成員。
做爲最佳實踐,導入的模塊應該保存在常量中,由於咱們有可能意外的將logger
從新賦值。
var logger = require('./logger')
logger = 1 // 從新賦值
logger.log('message')
複製代碼
從新賦值爲1
後,咱們再調用log
方法,就會發生異常
TypeError: logger.log is not a function 複製代碼
做爲對比,咱們把它定義爲一個常量
const logger = require('./logger') // 使用const定義爲常量
logger = 1 // 從新賦值
logger.log('message')
複製代碼
再運行程序,獲得另外一個異常,試圖給常量賦值,有些專門檢查這類錯誤的工具,使用它們能夠避免在運行時出現問題,好比jshint
。
TypeError: Assignment to constant variable.
複製代碼
若是咱們意外的重寫了常量的值,咱們會在查錯時而不是運行時獲得報錯,這就是定義爲常量的好處。
若是咱們不想導出一個對象,只想導出一個簡單的函數,好比在logger
模塊中,咱們不須要導出一個對象,咱們只有一個簡單的函數,對象在有多個屬性或方法時才須要用獲得。
咱們能夠將exports
直接賦值爲log
函數。
var url = 'http://mylogger.io/log'
function log(message) {
console.log(message)
}
module.exports = log
複製代碼
這樣以後,咱們在app.js
的logger
就再也不是一個對象,它是一個咱們能夠直接調用的函數,咱們命名爲log
會更好。
const log = require('./logger')
log('message') // 直接調用
複製代碼
因此在你的模塊中,你能夠導出對象或者單一的函數。
咱們已經知道了Node
模塊中定義的變量和函數的做用域只在當前模塊內,Node
是如何實現的呢?
實際上,Node
並無直接運行代碼,而是包裝在一個函數中,在運行時,咱們的代碼被轉換成這樣,咱們拿logger
模塊來舉例。
(function (exports, require, module, __filename, __dirname) {
var url = 'http://mylogger.io/log'
function log(message) {
console.log(message)
}
module.exports = log
})
複製代碼
若是你是一個有經驗的JavaScript
開發者,你可能知道這是當即調用函數表達式,也叫作IIFE。若是你不清楚也沒關係,這並非Node
的內容,這邊想表達的是Node
不直接執行代碼,Node
老是將代碼包裹在這樣的一個函數中。
看看這個函數的參數,看下require
,這個require
看起來像全局的但實際不是,事實上它是每一個模塊本地的,在每一個模塊中,require
都是做爲參數傳給函數,咱們稱之爲模塊包裝函數
。
還有module
參數,還有module.exports
簡寫爲exports
,因此當你想將函數公開的時候能夠這麼寫
module.exports.log = log
複製代碼
也能夠這麼寫
exports.log = log
複製代碼
可是若是沒有module對象引用就不能重置exports
對象,換句話說,不能給exports
對象賦值。
exports = log // 不要這麼寫
複製代碼
由於這個exports
是module.exports
的一個引用,你不能更改它的引用。
還有__filename
和__dirname
分別表明文件名和目錄名,咱們打印出來看一下。
console.log(__filename)
console.log(__dirname)
var url = 'http://mylogger.io/log'
function log(message) {
console.log(message)
}
module.exports = log
複製代碼
回到控制檯,運行程序,打印結果以下
F:\2020\study\Node.js\node-course\first-app\logger.js
F:\2020\study\Node.js\node-course\first-app
message
複製代碼
第一個是文件名,第二個是文件的完整路徑。
如今,咱們對Node
模塊和它的運做方式有個大概的印象了,咱們知道了如何建立它們和加載它們。
Node
包含了不少有用和經常使用的模塊,涉及的知識點較多,下篇文章咱們再一塊兒來學習下。
感謝您的閱讀,但願對你有所幫助。因爲本人水平有限,若是文中有描述不當的地方,煩請指正,很是感謝。
歡迎你們關注個人公衆號前端幫幫忙
。
Node的模塊系統(路徑模塊、操做系統模塊、文件模塊、事件模塊、HTTP模塊)