一、什麼是fs模塊?

在Node.js中,文件系統(File System)是處理文件和目錄的核心模塊,我們通常通過fs模塊來操作。想象一下,如果你用瀏覽器寫前端代碼,只能在瀏覽器中操作內存裏的數據,但Node.js是運行在服務器端的,它可以直接和電腦的硬盤打交道,而fs模塊就是Node.js提供的“硬盤操作工具包”。

比如,我們可以用它讀取本地的JSON文件、創建文件夾、寫入日誌等。fs模塊的API分爲同步和異步兩種,同步方法會阻塞代碼執行直到完成,而異步方法則不會,更適合高併發場景。

二、同步vs異步:該選哪種?

在開始寫代碼前,先搞清楚同步和異步的區別:

  • 同步方法:像排隊買奶茶,你必須站在那等,直到拿到奶茶才能繼續。特點是簡單直接,但會阻塞後續代碼。
  • 異步方法:像提前點單,下單後可以去做別的事,奶茶做好了會通知你。特點是非阻塞,效率更高,適合Node.js的單線程模型。

初學者建議:優先學習異步方法,它更符合Node.js的設計理念。如果邏輯簡單(比如讀取小文件),也可以用同步方法快速實現。

三、基礎文件讀寫操作

1. 引入fs模塊

首先,在代碼裏引入fs模塊,就像引入工具包一樣:

const fs = require('fs'); // 引入核心模塊

2. 異步讀取文件(readFile)

fs.readFile方法異步讀取文件,語法如下:

fs.readFile('文件路徑', '編碼格式', (err, data) => {
  // 回調函數會在讀取完成後執行
  if (err) {
    console.error('讀取失敗:', err);
    return; // 出錯時終止後續操作
  }
  console.log('文件內容:', data);
});

參數解釋
- 文件路徑:可以是相對路徑(如'./test.txt')或絕對路徑(如'/Users/yourname/test.txt')。
- 編碼格式:如'utf8'(返回字符串),不寫則返回Buffer(二進制數據)。
- 回調函數:接收兩個參數:err(錯誤信息)和data(文件內容)。

示例:創建一個test.txt文件,內容爲"Hello, Node.js!",執行以下代碼:

const fs = require('fs');

// 異步讀取test.txt(相對路徑)
fs.readFile('./test.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('讀取失敗:', err);
    return;
  }
  console.log('文件內容:', data); // 輸出:文件內容: Hello, Node.js!
});

3. 同步讀取文件(readFileSync)

如果想讓代碼“順序執行”,可以用同步方法fs.readFileSync,錯誤用try/catch捕獲:

try {
  const data = fs.readFileSync('./test.txt', 'utf8');
  console.log('同步讀取內容:', data);
} catch (err) {
  console.error('同步讀取失敗:', err);
}

區別:同步方法會阻塞後續代碼,直到文件讀取完成。適合簡單邏輯,比如初始化配置文件時。

4. 異步寫入文件(writeFile)

fs.writeFile寫入文件,默認會覆蓋已有內容(如果文件不存在則自動創建):

fs.writeFile('output.txt', '這是寫入的內容', (err) => {
  if (err) {
    console.error('寫入失敗:', err);
    return;
  }
  console.log('寫入成功!');
});

示例:執行後會在當前目錄生成output.txt,內容爲"這是寫入的內容"

注意:如果想追加內容而不是覆蓋,用fs.appendFile

fs.appendFile('output.txt', '\n這是追加的內容', (err) => {
  if (err) {
    console.error('追加失敗:', err);
    return;
  }
  console.log('追加成功!');
});

5. 路徑處理:避免“找不到文件”的坑

路徑是文件操作的關鍵,Node.js中path模塊可以幫我們更安全地處理路徑(如跨平臺拼接路徑)。先簡單引入path模塊:

const path = require('path');

常用路徑方法
- path.join(__dirname, 'subdir', 'file.txt'):拼接路徑,自動處理/\(Windows系統自動轉換)。
- __dirname:當前模塊所在目錄的絕對路徑(動態變量,永遠指向當前文件所在文件夾)。
- __filename:當前模塊的絕對路徑(包含文件名)。

示例:用path.join拼接路徑:

const fs = require('fs');
const path = require('path');

// 拼接路徑:__dirname + 'data' + 'test.txt'
const filePath = path.join(__dirname, 'data', 'test.txt');

// 讀取該路徑下的文件
fs.readFile(filePath, 'utf8', (err, data) => {
  if (err) {
    console.error('路徑錯誤:', err);
    return;
  }
  console.log('正確讀取到文件:', data);
});

四、目錄操作:創建、讀取、刪除

1. 創建目錄(mkdir)

fs.mkdir創建目錄,默認只創建最外層目錄,嵌套目錄需要遞歸創建:

fs.mkdir('./newDir', (err) => {
  if (err) {
    console.error('創建目錄失敗:', err);
    return;
  }
  console.log('目錄創建成功!');
});

遞歸創建多級目錄:需要加recursive: true參數:

fs.mkdir('./a/b/c', { recursive: true }, (err) => { // 允許創建嵌套目錄
  if (err) {
    console.error('創建多級目錄失敗:', err);
    return;
  }
  console.log('多級目錄創建成功!');
});

2. 讀取目錄內容(readdir)

fs.readdir列出目錄下的所有文件和子目錄:

fs.readdir('./newDir', (err, files) => {
  if (err) {
    console.error('讀取目錄失敗:', err);
    return;
  }
  console.log('目錄內容:', files); // 輸出數組,如 ['file1.txt', 'subDir']
});

3. 刪除目錄(rmdir)

fs.rmdir刪除目錄,僅支持刪除空目錄

fs.rmdir('./emptyDir', (err) => { // 確保目錄爲空才能刪除
  if (err) {
    console.error('刪除目錄失敗:', err);
    return;
  }
  console.log('目錄刪除成功!');
});

五、文件信息與權限檢查

1. 檢查文件是否存在(existsSync)

fs.existsSync快速檢查文件/目錄是否存在(同步方法):

const fs = require('fs');
const path = require('path');

const filePath = path.join(__dirname, 'test.txt');

if (fs.existsSync(filePath)) {
  console.log('文件存在!');
  fs.readFile(filePath, 'utf8', (err, data) => { /* ... */ });
} else {
  console.log('文件不存在!');
}

2. 獲取文件/目錄信息(stat)

fs.stat獲取文件詳細信息(大小、創建時間、是否爲目錄等):

fs.stat('./test.txt', (err, stats) => {
  if (err) {
    console.error('獲取信息失敗:', err);
    return;
  }
  console.log('是否爲文件:', stats.isFile()); // true
  console.log('文件大小:', stats.size, '字節'); // 15字節(示例)
});

六、常見問題與解決

1. 文件路徑錯誤怎麼辦?

原因:相對路徑的基準是“執行node命令的目錄”,而非腳本所在目錄。
解決:用__dirname(當前腳本所在目錄)拼接路徑,或直接用絕對路徑。

// 錯誤示例(可能找不到文件)
fs.readFile('test.txt', ...); // 依賴執行node命令的位置

// 正確示例
fs.readFile(path.join(__dirname, 'test.txt'), ...); // 基於腳本位置的絕對路徑

2. 如何處理大文件?

小文件用readFile/writeFile即可,但大文件(如視頻、日誌)會佔用內存。此時推薦使用流(Stream),它會分塊讀取數據:

const fs = require('fs');
const readStream = fs.createReadStream('largeFile.txt');
const writeStream = fs.createWriteStream('copiedFile.txt');

// 管道流:數據從readStream流向writeStream
readStream.pipe(writeStream);

七、總結

fs模塊是Node.js操作文件系統的基石,掌握它你就能實現:

  • 讀取配置文件、日誌文件
  • 創建/刪除目錄、文件
  • 處理文件內容(讀寫、追加、複製)

實踐建議
1. 先從簡單的“創建文件→寫入內容→讀取內容→刪除文件”開始
2. 嘗試用path模塊處理不同平臺的路徑問題
3. 對比同步/異步方法的區別,理解事件循環和非阻塞I/O的優勢

記住,編程學習沒有捷徑,多敲代碼、多踩坑、多調試,才能真正掌握文件系統操作的精髓!

小夜