檔案系統是一種用於向用戶提供底層資料存取的機制,同時也是一套實現了資料的儲存、分級組織、存取和獲取等操作的抽象資料型別。
Node.js 中的fs模組就是對檔案系統的封裝,整合了一套標準 POSIX 檔案 I/O 操作的集合,包括檔案的讀寫、刪除、遍歷、重新命名等操作。
fs 模組中的所有方法都提供了三種形式:回撥、同步和 Promise ,其中 Promise 是在 Node.js 的版本 10 中引入的。
本系列所有的範例原始碼都已上傳至Github,點選此處獲取。
在回撥形式的方法中,最後一個引數是其回撥函數,會非同步地呼叫,其中回撥函數的第一個引數始終為異常預留,不過有個例外是 exists() 方法。
回撥形式不容易書寫,很容易就會形成回撥地獄。
雖然同步形式的方法比較容易書寫,但是在執行時會阻止 Node.js 事件迴圈和阻塞 JavaScript 執行,直到操作完成。
Promise 形式的方法會使用底層的 Node.js 執行緒池,在事件迴圈執行緒之外非同步地執行檔案系統操作。對同一檔案執行多個並行修改時必須小心,有可能會損壞資料。
以讀取檔案為例,三種形式的寫法如下所示,若不指定編碼,那麼輸出的將是 Buffer 範例。
const fs = require('fs'); // 回撥 fs.readFile('./data.txt', 'utf8', (err, data) => { if (err) throw err; console.log(data); // strick }); // 同步 const data = fs.readFileSync('./data.txt', 'utf8'); console.log(data); // strick // Promise const { promises } = fs; async function readFilePromise() { const data = await promises.readFile('./data.txt', 'utf8'); console.log(data); // strick } readFilePromise();
1)判斷檔案是否存在
exists() 方法可用於判斷檔案是否存在,但在上一小節中曾提到,它已被棄用。
這是因為此回撥的引數與其他回撥不一致。通常,Node.js 回撥的第一個引數是 err 引數,然後跟可選的其他引數,但 fs.exists() 回撥只有一個布林引數,如下所示。
fs.exists('./data.txt', isExist => {
console.log(isExist);
});
再則是因為 exists() 方法的功能用 access() 方法也能實現,其內部原始碼如下所示,其實也是呼叫了 access() 方法。
function exists(path, callback) { maybeCallback(callback); // 構造回撥函數 function suppressedCallback(err) { callback(err ? false : true); } try { fs.access(path, F_OK, suppressedCallback); } catch { return callback(false); } }
其中 F_OK 是 fs 模組中的一個常數,表示檔案是否存在,使用方法如下所示,R_OK 表示是否可讀,W_OK 表示是否可寫。
const { constants } = require('fs');
const { F_OK, R_OK, W_OK } = constants;
注意,在呼叫 fs.open()、fs.readFile() 或 fs.writeFile() 之前,不能使用 fs.access() 檢查檔案是否存在。
因為這樣做會引入競爭條件,其他程序可能會在兩次呼叫之間修改檔案狀態,造成非預期的結果。
遇到這種場景,推薦的做法是直接開啟、讀取或寫入檔案,當檔案不可用時再做處理。
另一種判斷檔案是否存在的方法是呼叫 stat(),讀取檔案屬性。
它有兩個方法 isDirectory() 和 isFile() 可分別判斷是否是目錄和是否是檔案,如下所示。
fs.stat('./data.txt', (err, stats) => {
console.log(stats.isDirectory());
console.log(stats.isFile());
});
同樣要注意的是,它也不能在呼叫 fs.open()、fs.readFile() 或 fs.writeFile() 之前,檢查檔案是否存在。
2)方法
下面羅列的是 fs 模組的一些方法。
3)路徑
路徑處理並不是在 fs 模組,而是在path模組,它的方法包括。
path.basename('../06/data.txt') // data.txt path.dirname('../06/data.txt'); // ../06 path.extname('../06/data.txt'); // .txt path.isAbsolute('../06/data.txt'); // false path.join('../', '06', 'data.txt'); // ../06/data.txt path.normalize('/../06/data.txt'); // /06/data.txt // { root: '', dir: '../06', base: 'data.txt', ext: '.txt', name: 'data' } path.parse('../06/data.txt'); path.relative('../', '../06/data.txt'); // 06/data.txt path.resolve('../06/data.txt'); // /Users/code/web/node/06/data.txt
參考資料: