瀏覽器 File API 深度解析:本機檔案處理的隱私架構
瀏覽器檔案處理的演進
早期 Web 應用處理檔案依賴 <input type="file"> + 表單上傳到伺服器。2010 年後,HTML5 File API 讓 JavaScript 可以直接讀取使用者本機檔案——無需上傳。
這一變革催生了「瀏覽器本機工具」品類:PDF 編輯、圖片壓縮、影片轉碼全部在使用者裝置上完成,檔案永不離開瀏覽器。
核心 API 層次
File (使用者選擇的檔案,含 name/size/type/lastModified)
↓ extends
Blob (二進位大型物件,含 size/type)
↓ 讀取為
ArrayBuffer (原始位元組,適合 WASM/加密)
↓ 或
Text (UTF-8 字串,適合 JSON/CSV)
↓ 或
Data URL (Base64 編碼,適合內嵌展示)
↓ 輸出為
Blob → Object URL → 下載 / 預覽
File vs Blob
// File 是 Blob 的子類別,多了檔名和修改時間
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', (e) => {
const file: File = e.target.files[0];
console.log(file.name); // "report.pdf"
console.log(file.size); // 2048576 (bytes)
console.log(file.type); // "application/pdf"
console.log(file.lastModified); // 1717200000000
});
讀取方式對比
| 方法 | 回傳類型 | 適用場景 | 記憶體特點 |
|---|---|---|---|
file.text() |
string | JSON、CSV、文字 | 全量載入 |
file.arrayBuffer() |
ArrayBuffer | PDF、圖片、WASM 輸入 | 全量載入 |
file.stream() |
ReadableStream | 大檔案串流處理 | 分塊讀取 |
FileReader.readAsDataURL() |
Data URL | 圖片預覽 | Base64 膨脹 33% |
工具庫的策略:< 50MB 用 arrayBuffer(),> 50MB 用 stream() 分塊處理。
Object URL:零拷貝預覽與下載
// 建立暫時 URL(記憶體參考,非磁碟檔案)
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
// 用於預覽
iframe.src = url;
// 用於下載
const a = document.createElement('a');
a.href = url;
a.download = 'merged.pdf';
a.click();
// ⚠️ 必須釋放,否則記憶體洩漏
URL.revokeObjectURL(url);
Object URL 是 blob: 協定的特殊 URL,指向記憶體中的 Blob 物件。每個未 revoke 的 URL 都會佔用記憶體——批次處理時必須及時釋放。
拖曳上傳的實作
const dropZone = document.getElementById('drop-zone');
dropZone.addEventListener('dragover', (e) => {
e.preventDefault(); // 必須阻止,否則瀏覽器會開啟檔案
e.dataTransfer.dropEffect = 'copy';
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
const files = Array.from(e.dataTransfer.files);
const pdfs = files.filter(f => f.type === 'application/pdf');
processFiles(pdfs);
});
DataTransfer.files 回傳 FileList,與 <input type="file"> 的 files 屬性完全相容。
隱私架構:為什麼本機處理更安全
傳統線上工具的資料流
使用者檔案 → HTTPS 上傳 → 伺服器處理 → 下載結果 → 伺服器刪除(聲稱)
↑
可能被快取、記錄、備份
工具庫的資料流
使用者檔案 → File API 讀取 → 瀏覽器記憶體/WASM 處理 → Blob 下載
↑
永不離開使用者裝置
| 對比維度 | 伺服器處理 | 瀏覽器本機處理 |
|---|---|---|
| 檔案傳輸 | 需要上傳+下載 | 零網路傳輸 |
| 隱私風險 | 伺服器可能留存 | 關閉分頁即清除 |
| 速度 | 受網速限制 | 僅受 CPU 限制 |
| 檔案大小 | 通常 10-50MB 限制 | 受裝置記憶體限制 |
| 離線可用 | 否 | 是(PWA 快取後) |
Streams API:處理超大檔案
對於超過 500MB 的影片檔案,全量載入到 ArrayBuffer 可能導致 OOM:
async function processLargeFile(file: File) {
const stream = file.stream();
const reader = stream.getReader();
const chunks: Uint8Array[] = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value); // 每次只載入一個 chunk(通常 64KB)
}
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
FFmpeg.wasm 支援從 Uint8Array 寫入虛擬檔案系統,配合 Streams 可以處理數 GB 的影片——只要裝置記憶體足夠。
檔案類型偵測:MIME vs 魔數
File.type 來自作業系統副檔名對應,不可靠:
// ❌ 使用者可以將 .exe 重新命名為 .pdf
file.type === 'application/pdf' // 可能為 true,但實際不是 PDF
// ✅ 讀取檔案頭魔數
const header = new Uint8Array(await file.slice(0, 5).arrayBuffer());
const isPdf = header[0] === 0x25 && header[1] === 0x50 &&
header[2] === 0x44 && header[3] === 0x46; // %PDF
工具庫的 檔案類型嗅探工具 就是基於魔數偵測,比副檔名更準確。
下載最佳實務
觸發下載
function downloadBlob(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
批次下載:ZIP 打包
多個檔案不應逐個觸發下載(瀏覽器會阻止彈出視窗)。工具庫使用 JSZip 將多個結果打包為一個 ZIP:
PDF 轉圖片 → 50 張 PNG → JSZip 打包 → 下載 images.zip
總結
File API 是瀏覽器本機工具的技術基石。理解 File → Blob → ArrayBuffer 的轉換鏈、Object URL 的生命週期管理、以及 Streams 的大檔案處理策略,是建構隱私優先 Web 工具的核心能力。工具庫 200+ 工具全部基於這套架構,實現了「檔案不上傳、處理在本機、關閉即清除」的隱私承諾。
本站提供瀏覽器本地工具,免註冊即可試用 →