一. 內建模組path1.1. 認識path模組
path模組用於對路徑和檔案進行處理,提供了很多好用的方法。
並且我們知道在Mac OS、Linux和window上的路徑是不一樣的
window上會使用 \或者 \\ 來作為檔案路徑的分隔符,當然目前也支援 /;在Mac OS、Linux的Unix作業系統上使用 / 來作為檔案路徑的分隔符;那麼如果我們在window上使用 \ 來作為分隔符開發了一個應用程式,要部署到Linux上面應該怎麼辦呢?
顯示路徑會出現一些問題;所以為了遮蔽他們之間的差異,在開發中對於路徑的操作我們可以使用path 模組;1.2. path常見的API從路徑中獲取資訊
dirname:獲取檔案的父資料夾;basename:獲取檔名;extname:獲取副檔名;const path = require("path");const myPath = '/Users/coderwhy/Desktop/Node/課堂/PPT/01_邂逅Node.pdf';const dirname = path.dirname(myPath);const basename = path.basename(myPath);const extname = path.extname(myPath);console.log(dirname); // /Users/coderwhy/Desktop/Node/課堂/PPTconsole.log(basename); // 01_邂逅Node.pdfconsole.log(extname); // .pdf
路徑的拼接
如果我們希望將多個路徑進行拼接,但是不同的作業系統可能使用的是不同的分隔符;這個時候我們可以使用path.join函式;console.log(path.join('/user', 'why', 'abc.txt'));
將檔案和某個資料夾拼接
如果我們希望將某個檔案和資料夾拼接,可以使用 path.resolve;resolve函式會判斷我們拼接的路徑前面是否有 /或../或./;如果有表示是一個絕對路徑,會返回對應的拼接路徑;如果沒有,那麼會和當前執行檔案所在的資料夾進行路徑的拼接path.resolve('abc.txt'); // /Users/coderwhy/Desktop/Node/TestCode/04_learn_node/06_常見的內建模組/02_檔案路徑/abc.txtpath.resolve('/abc.txt'); // /abc.txtpath.resolve('/User/why', 'abc.txt'); // /User/why/abc.txtpath.resolve('User/why', 'abc.txt'); // /Users/coderwhy/Desktop/Node/TestCode/04_learn_node/06_常見的內建模組/02_檔案路徑/User/why/abc.txt
resolve其實我們在webpack中也會使用:
const CracoLessPlugin = require('craco-less');const path = require("path");const resolve = dir => path.resolve(__dirname, dir);module.exports = { plugins: [ { plugin: CracoLessPlugin, options: { lessLoaderOptions: { lessOptions: { modifyVars: { '@primary-color': '#1DA57A' }, javascriptEnabled: true, }, }, }, } ], webpack: { alias: { "@": resolve("src"), "components": resolve("src/components") } }}
二. 內建模組fs1.1. 認識fs模組fs是File System的縮寫,表示檔案系統。
對於任何一個為伺服器端服務的語言或者框架通常都會有自己的檔案系統:
因為伺服器需要將各種資料、檔案等放置到不同的地方;比如使用者資料可能大多數是放到資料庫中的(後面我們也會學習);比如某些配置檔案或者使用者資源(圖片、音影片)都是以檔案的形式存在於作業系統上的;Node也有自己的檔案系統操作模組,就是fs:
藉助於Node幫我們封裝的檔案系統,我們可以在任何的作業系統(window、Mac OS、Linux)上面直接去操作檔案;這也是Node可以開發伺服器的一大原因,也是它可以成為前端自動化指令碼等熱門工具的原因;Node檔案系統的API非常的多:https://nodejs.org/dist/latest-v14.x/docs/api/fs.html
我們不可能,也沒必要一個個去學習;這個更多的應該是作為一個API查詢的手冊,等用到的時候查詢即可;學習階段我們只需要學習最常用的即可;但是這些API大多數都提供三種操作方式:
方式一:同步操作檔案:程式碼會被阻塞,不會繼續執行;方式二:非同步回撥函式操作檔案:程式碼不會被阻塞,需要傳入回撥函式,當獲取到結果時,回撥函式被執行;方式三:非同步Promise操作檔案:程式碼不會被阻塞,透過 fs.promises呼叫方法操作,會返回一個Promise,可以透過then、catch進行處理;我們這裡以獲取一個檔案的狀態為例:
注意:都需要引入 fs 模組;方式一:同步操作檔案
// 1.方式一: 同步讀取檔案const state = fs.statSync('../foo.txt');console.log(state);console.log('後續程式碼執行');
方式二:非同步回撥函式操作檔案
// 2.方式二: 非同步讀取fs.stat("../foo.txt", (err, state) => { if (err) { console.log(err); return; } console.log(state);})console.log("後續程式碼執行");
方式三:非同步Promise操作檔案
// 3.方式三: Promise方式fs.promises.stat("../foo.txt").then(state => { console.log(state);}).catch(err => { console.log(err);})console.log("後續程式碼執行");
後續程式碼演練中,我將以非同步回撥的方式演練:相對更通用一些;
1.2. 檔案描述符檔案描述符(File descriptors)是什麼呢?
在 POSIX 系統上,對於每個程序,核心都維護著一張當前開啟著的檔案和資源的表格。
每個開啟的檔案都分配了一個稱為檔案描述符的簡單的數字識別符號。在系統層,所有檔案系統操作都使用這些檔案描述符來標識和跟蹤每個特定的檔案。Windows 系統使用了一個雖然不同但概念上類似的機制來跟蹤資源。為了簡化使用者的工作,Node.js 抽象出操作系統之間的特定差異,併為所有開啟的檔案分配一個數字型的檔案描述符。fs.open() 方法用於分配新的檔案描述符。一旦被分配,則檔案描述符可用於從檔案讀取資料、向檔案寫入資料、或請求關於檔案的資訊。
// 獲取檔案描述符fs.open("../foo.txt", 'r', (err, fd) => { console.log(fd); fs.fstat(fd, (err, state) => { console.log(state); })})
1.3. 檔案的讀寫如果我們希望對檔案的內容進行操作,這個時候可以使用檔案的讀寫:
fs.readFile(path[, options], callback):讀取檔案的內容;fs.writeFile(file, data[, options], callback):在檔案中寫入內容;檔案寫入:
fs.writeFile('../foo.txt', content, {}, err => { console.log(err);})
在上面的程式碼中,你會發現有一個大括號沒有填寫任何的內容,這個是寫入時填寫的option引數:
flag:寫入的方式。encoding:字元的編碼;我們先來看flag:
flag的值有很多:https://nodejs.org/dist/latest-v14.x/docs/api/fs.html#fs_file_system_flagsw 開啟檔案寫入,預設值;w+開啟檔案進行讀寫,如果不存在則建立檔案;r+ 開啟檔案進行讀寫,如果不存在那麼丟擲異常;r開啟檔案讀取,讀取時的預設值;a開啟要寫入的檔案,將流放在檔案末尾。如果不存在則建立檔案;a+開啟檔案以進行讀寫,將流放在檔案末尾。如果不存在則建立檔案我們再來看看編碼:
我之前在簡書上寫過一篇關於字元編碼的文章:https://www.jianshu.com/p/899e749be47c目前基本用的都是UTF-8編碼;檔案讀取:
如果不填寫encoding,返回的結果是Buffer;fs.readFile('../foo.txt', {encoding: 'utf-8'}, (err, data) => { console.log(data);})
檔案讀取:
const fs = require('fs');fs.readFile('../foo.txt', {encoding: 'utf-8'}, (err, data) => { console.log(data);})
1.4. 資料夾操作
新建一個資料夾
使用fs.mkdir()或fs.mkdirSync()建立一個新資料夾:
const fs = require('fs');const dirname = '../why';if (!fs.existsSync(dirname)) { fs.mkdir(dirname, (err) => { console.log(err); })}
獲取資料夾的內容
// 讀取資料夾function readFolders(folder) { fs.readdir(folder, {withFileTypes: true} ,(err, files) => { files.forEach(file => { if (file.isDirectory()) { const newFolder = path.resolve(dirname, file.name); readFolders(newFolder); } else { console.log(file.name); } }) })}readFolders(dirname);
檔案重新命名
fs.rename('../why', '../coder', err => { console.log(err);})
三. 內建模組events3.1. 基本使用
Node中的核心API都是基於非同步事件驅動的:
在這個體系中,某些物件(發射器(Emitters))發出某一個事件;我們可以監聽這個事件(監聽器 Listeners),並且傳入的回撥函式,這個回撥函式會在監聽到事件時呼叫;發出事件和監聽事件都是透過EventEmitter類來完成的,它們都屬於events物件。
emitter.on(eventName, listener):監聽事件,也可以使用addListener;emitter.off(eventName, listener):移除事件監聽,也可以使用removeListener;emitter.emit(eventName[, ...args]):發出事件,可以攜帶一些引數;const EventEmmiter = require('events');// 監聽事件const bus = new EventEmmiter();function clickHanlde(args) { console.log("監聽到click事件", args);}bus.on("click", clickHanlde);setTimeout(() => { bus.emit("click", "coderwhy"); bus.off("click", clickHanlde); bus.emit("click", "kobe");}, 2000);
3.2. 常見的屬性EventEmitter的例項有一些屬性,可以記錄一些資訊:
emitter.eventNames():返回當前 EventEmitter物件註冊的事件字串陣列;emitter.getMaxListeners():返回當前 EventEmitter物件的最大監聽器數量,可以透過setMaxListeners()來修改,預設是10;emitter.listenerCount(事件名稱):返回當前 EventEmitter物件某一個事件名稱,監聽器的個數;emitter.listeners(事件名稱):返回當前 EventEmitter物件某個事件監聽器上所有的監聽器陣列;console.log(bus.eventNames());console.log(bus.getMaxListeners());console.log(bus.listenerCount("click"));console.log(bus.listeners("click"));
3.3. 方法的補充
emitter.once(eventName, listener):事件監聽一次
const EventEmitter = require('events');const emitter = new EventEmitter();emitter.once('click', (args) => { console.log("監聽到事件", args);})setTimeout(() => { emitter.emit('click', 'coderwhy'); emitter.emit('click', 'coderwhy');}, 2000);
emitter.prependListener():將監聽事件新增到最前面
emitter.on('click', (args) => { console.log("a監聽到事件", args);})// b監聽事件會被放到前面emitter.prependListener("click", (args) => { console.log("b監聽到事件", args);})
emitter.prependOnceListener():將監聽事件新增到最前面,但是隻監聽一次
emitter.prependOnceListener("click", (args) => { console.log("c監聽到事件", args);})
emitter.removeAllListeners([eventName]):移除所有的監聽器
// 移除emitter上的所有事件監聽emitter.removeAllListeners();// 移除emitter上的click事件監聽emitter.removeAllListeners("click");