一次搞懂 JS 的二進位資料處理:Blob、File、FileReader、ArrayBuffer 到 Buffer 全解析
初探 JS 二進制:基礎概念與關鍵類型
什麼是二進制資料?
在我們的日常生活中,文字、圖片、音樂、甚至影片,對我們來說可能很直觀,但電腦其實看不到這些「豐富的內容」,它只能看到由 0 和 1 組成的數據——這就是所謂的二進制資料。
簡單來說,二進制資料就是電腦世界裡的「原始語言」。無論是你在網頁上點擊下載的文件,還是後端伺服器處理的數據流,這些其實都脫不開二進制資料的範疇。
你可能會想,既然這些由 0 和 1 組成的二進制資料只有電腦本人看得懂,那麼在什麼情況下我們會需要去操作他們呢?以前端來說,以下這些都是常見的需求場景:
- 上傳或下載文件(比如圖片、PDF)。
- 將圖片或音訊處理後傳輸到伺服器。
- 處理流式數據,比如大文件的分塊上傳。
儘管 JavaScript 主要用於處理字符串和 JSON 等高階資料格式,但它其實也提供了一套完整的工具,來處理更底層的二進制資料。
JS 的二進制資料家族
現在,讓我們來看看 JavaScript 提供的這些「工具」。我把它們整理成了一張表,這樣你可以一目了然地了解每個工具的功能和用途:
ArrayBuffer
├─ DataView
└─ TypedArray
├─ Uint8Array
├─ Int16Array
└─ ...
Blob
├─ File
├─ FileList
└─ FileReader
Buffer (Node.js 環境)
| 成員 | 用途與特點 | 常見使用場景 |
|---|---|---|
| ArrayBuffer | 固定大小的二進制緩衝區,是其他類型的基礎。 | 圖片處理、音訊處理、網路數據傳輸 |
| TypedArray | 基於 ArrayBuffer 的結構化數組,支持操作不同類型的數據(如整數或浮點數)。 | 編碼或解碼多媒體數據,例如影像像素或音訊波形 |
| DataView | 提供更靈活的方式操作 ArrayBuffer,可以按需定義數據結構。 | 解析複雜的二進制數據格式,如自定義檔案格式或網路協議 |
| Blob | 儲存二進制數據的文件對象,可以方便地用於文件下載或傳輸。 | 文件上傳、圖像處理、後端資料接收 |
| File | 繼承自 Blob,用於描述用戶選擇的文件,比如通過 <input type="file"> 選擇的檔案。 | 文件上傳 |
| FileList | 表示多個文件的集合,通常來自 <input type="file" multiple>。 | 批量文件上傳 |
| FileReader | 讀取文件內容,支持讀取成多種格式(如文字、ArrayBuffer、DataURL)。 | 預覽文件、讀取文件數據 |
| Buffer | Node.js 環境專屬,用於高效處理二進制數據。 | 網路請求、資料庫數據處理 |
二進制資料的協作關聯圖解

這些工具並非獨立存在,而是相互協作,幫助我們完成不同任務。比如,當你需要上傳一張圖片時:
- 使用者選擇圖片,產生一個
File物件。 - 使用
FileReader將檔案內容讀取為ArrayBuffer。 - 如果需要進一步處理,可以將
ArrayBuffer轉換為TypedArray,用來操作像素資料。 - 最後,將處理後的資料重新封裝成
Blob,供使用者下載或傳輸。
Blob:從檔案到資料流的核心
Blob 的定義與特性
在 JavaScript 世界裡,Blob 是處理二進制文件的核心工具。它的全名是 Binary Large Object,意即「二進位大型物件」。簡單 來說,Blob 代表的是一塊不可變的二進制資料,這些資料可能是文字、圖像、音訊甚至影片。這些資料通常是由一個或多個 ArrayBuffer 或 DOMString 組成的。
Blob 是不可修改的。如果需要操作它的內容,必須透過工具如 FileReader 來讀取並生成新的 Blob。
如何創建 Blob?
JavaScript 提供了一個簡單的方法來建立 Blob,使用它的建構函式:
new Blob(blobParts[, options])
- blobParts:
- 可以由
ArrayBuffer、ArrayBufferView、Blob或DOMString組成的Array物件
- 可以由
- options:
type屬性,預設值為空字串"",表示將被放進Blob物件的陣列內容之 MIME 類型。endings屬性,表示包含\n換行字元的字串要如何輸出,預設值為字串"transparent"。
範例:創建一個文字檔案的 Blob
const text = "Hello, Blob!";
const textBlob = new Blob([text], { type: "text/plain" });
console.log(textBlob); // Blob {size: 12, type: "text/plain"}
Blob 的屬性與方法
- 屬性
size: 表示儲存的資料,總共佔了多少位元(byte)。type: 表示儲存的資料格式(MIME type)。
- 方法
slice:用於將一個 Blob 切分成多個子 Blob。
範例:分割 Blob
const textBlob = new Blob(["Hello, world!"], { type: "text/plain" });
console.log(textBlob.size); // 13 (字元數加空格與符號)
console.log(textBlob.type); // text/plain
const largeBlob = new Blob(["This is a large blob"], { type: "text/plain" });
const slicedBlob = largeBlob.slice(0, 4); // 包含 "This"
console.log(await slicedBlob.text()); // "This"
實戰:Blob 的常見應用
1. 文件下載
在前端中,可以利用 URL.createObjectURL 為 Blob 生成臨時的 URL,並賦值給 <a> 標籤的 href 屬性來實現文件下載。
React 前端示範:
import React from "react";
const DownloadFile: React.FC = () => {
const handleDownload = () => {
const blob = new Blob(["This is a test file."], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "test.txt";
a.click();
URL.revokeObjectURL(url); // 釋放資源
};
return <button onClick={handleDownload}>下載檔案</button>;
};
export default DownloadFile;
2. 圖片顯示
類似文件下載,我們可以將 Blob 的 URL 賦值給圖片的 src 屬性,來顯示圖片。
React 前端示範:
import React, { useState } from "react";
const DisplayImage: React.FC = () => {
const [imageUrl, setImageUrl] = useState<string | null>(null);
const handleUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const url = URL.createObjectURL(file);
setImageUrl(url);
}
};
return (
<div>
<input type="file" accept="image/*" onChange={handleUpload} />
{imageUrl && <img src={imageUrl} alt="Uploaded" style={{ maxWidth: "100%" }} />}
</div>
);
};
export default DisplayImage;
3. 資源分段上傳
分段上傳是一種有效解決大文件上傳的方法。我們可以利用 Blob.slice 將文件切成多個片段,然後通過 API 一個個上傳。
Express 後端示範:
假設我們接收前端分段上傳的文件片段,並最終合併成完整文件。
import express, { Request, Response } from "express";
import fs from "fs";
import path from "path";
const app = express();
const UPLOAD_DIR = path.join(__dirname, "uploads");
app.use(express.json());
app.post("/upload", (req: Request, res: Response) => {
const { chunk, filename, index } = req.body;
const chunkPath = path.join(UPLOAD_DIR, `${filename}-${index}`);
fs.writeFileSync(chunkPath, Buffer.from(chunk, "base64")); // 將片段保存
res.status(200).send("Chunk uploaded");
});
app.post("/merge", (req: Request, res: Response) => {
const { filename, totalChunks } = req.body;
const filePath = path.join(UPLOAD_DIR, filename);
const writeStream = fs.createWriteStream(filePath);
for (let i = 0; i < totalChunks; i++) {
const chunkPath = path.join(UPLOAD_DIR, `${filename}-${i}`);
const data = fs.readFileSync(chunkPath);
writeStream.write(data);
fs.unlinkSync(chunkPath); // 刪除片段
}
writeStream.end();
res.status(200).send("File merged");
});
app.listen(3000, () => console.log("Server running on http://localhost:3000"));