Web Worker 實戰:如何在瀏覽器中安全卸載重計算任務

技术架构(更新於 2026年5月19日)

為什麼主執行緒會卡頓?

瀏覽器的主執行緒同時負責 JavaScript 執行、DOM 渲染、事件處理與版面配置。一旦某個 JS 任務耗時超過 50ms,使用者就會感知到卡頓——捲動不跟手、點擊無回應、動畫掉格。

典型的高耗時任務:

場景 耗時 主執行緒影響
PDF 逐頁渲染為圖片 2-30s 頁面完全凍結
FFmpeg 影片轉碼 10-120s 無法取消、UI 無回饋
PNG 批次壓縮 1-10s 輸入框無法回應
大型 JSON 格式化 100-500ms 短暫卡頓

Web Worker 的核心價值:將計算密集型任務移出主執行緒,保持 UI 始終可互動。


Web Worker 基礎架構

執行緒模型

┌─────────────────────────────────┐
│         Main Thread             │
│  DOM · Event Loop · Rendering   │
│         ↕ postMessage           │
├─────────────────────────────────┤
│         Web Worker              │
│  獨立 JS 上下文 · 無 DOM 存取    │
│  獨立 Event Loop                │
└─────────────────────────────────┘

Worker 擁有獨立的 JavaScript 執行環境,但無法存取 DOM、window、document。資料透過 postMessage 傳遞,大型物件使用 Transferable Objects 實現零拷貝轉移。

建立 Worker 的兩種方式

// 方式一:獨立檔案(推薦,便於程式碼分割)
const worker = new Worker(new URL('./pdf-worker.ts', import.meta.url));

// 方式二:Blob URL(適合動態產生)
const blob = new Blob([workerCode], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));

Next.js / Vite 推薦使用 new URL(..., import.meta.url) 模式,建置工具會自動處理 Worker 檔案的打包與 hash。


通訊模式:postMessage 與 Transferable

結構化複製 vs 轉移所有權

// ❌ 拷貝:大 ArrayBuffer 會被完整複製,雙倍記憶體
worker.postMessage({ buffer: largeArrayBuffer });

// ✅ 轉移:零拷貝,主執行緒立即失去存取權
worker.postMessage({ buffer: largeArrayBuffer }, [largeArrayBuffer]);

對於 PDF 二進位資料(通常 1-50MB),Transferable 是必選項。工具庫的 PDF 轉圖片功能在處理 100 頁 PDF 時,透過 Transferable 避免了 200MB+ 的記憶體拷貝。

請求-回應模式

// main.ts
const id = crypto.randomUUID();
worker.postMessage({ type: 'render', id, pageIndex: 0, pdfBytes }, [pdfBytes]);

worker.onmessage = (e) => {
  if (e.data.id === id) {
    const imageBlob = e.data.result;
    displayPreview(imageBlob);
  }
};

使用 UUID 關聯請求與回應,支援並行處理多個頁面。


工具庫中的 Worker 架構

PDF 轉圖片:多 Worker 並行

使用者上傳 PDF
     ↓
主執行緒:解析 PDF 頁數
     ↓
建立 Worker Pool(CPU 核心數 - 1)
     ↓
按頁分發渲染任務 → Worker 1, 2, 3, 4...
     ↓
主執行緒:收集結果 → 即時更新進度條
     ↓
打包 ZIP 下載

Worker 數量通常設為 navigator.hardwareConcurrency - 1,在並行度與記憶體佔用之間取得平衡。

FFmpeg.wasm:單 Worker + SharedArrayBuffer

影片轉碼使用 FFmpeg.wasm,它是一個大型 WASM 模組(~25MB)。由於 WASM 執行個體無法跨 Worker 共用,影片工具採用單 Worker 獨佔模式:

  • 主執行緒:UI 狀態、進度展示、取消控制
  • Worker:FFmpeg 轉碼、進度回呼
  • 透過 SharedArrayBuffer 共用進度資料(需 COOP/COEP 回應標頭)

錯誤處理與 Worker 生命週期

worker.onerror = (e) => {
  console.error('Worker error:', e.message);
  worker.terminate();
  fallbackToMainThread(); // 降級到主執行緒
};

// 任務完成後及時終止,釋放記憶體
worker.terminate();

Worker 不會自動 GC——長時間執行的 Worker 會持續佔用記憶體。任務完成後應呼叫 terminate() 或在 Worker 內部主動釋放大型物件。


Worker 的限制與替代方案

限制 影響 替代方案
無法存取 DOM 不能操作 UI postMessage 回傳結果
無法存取 localStorage 不能持久化 IndexedDB(Worker 可存取)
啟動開銷 ~50ms 短任務不划算 任務 < 50ms 留在主執行緒
除錯困難 DevTools 支援有限 console.log + 獨立 Worker 面板

對於極短的任務(如 JSON 格式化 < 10KB),Worker 的啟動開銷可能超過計算本身。工具庫的策略是:檔案 > 1MB 或預估耗時 > 100ms 時才啟用 Worker


效能實測

基於 Chrome 130,M2 MacBook Pro,50 頁 PDF 轉 PNG:

方案 總耗時 主執行緒阻塞 使用者可互動
主執行緒同步 18.2s 18.2s
單 Worker 17.8s 0s
4 Worker 並行 5.1s 0s

並行 Worker 將耗時從 18s 降至 5s,同時 UI 全程可回應——使用者可以取消任務、切換頁面、檢視已完成頁面的預覽。


最佳實務總結

  1. 按需啟用:短任務不必 Worker 化,避免啟動開銷
  2. Transferable 優先:大二進位資料必須轉移所有權
  3. Worker Pool:CPU 密集型任務用池化並行,I/O 密集型用單 Worker
  4. 及時終止:任務完成或取消後立即 terminate()
  5. 降級策略:Worker 不可用時回退到主執行緒 + requestIdleCallback
  6. 進度回饋:長任務必須透過 postMessage 回傳進度,避免使用者以為頁面當機

Web Worker 是建構高效能瀏覽器工具的關鍵基礎設施。配合 WASM 與 Transferable Objects,它讓瀏覽器端處理 GB 級檔案成為可能——這正是工具庫 200+ 工具全部在瀏覽器本機執行的技術基石。

本站提供瀏覽器本地工具,免註冊即可試用 →

#Web Worker#多线程#性能优化#浏览器#架构