本文 AI 產出,尚未審核

JavaScript – 效能與最佳化(Performance & Optimization)

主題:Web Worker


簡介

在前端開發中,我們常常會遇到 大量計算資料處理即時影像編碼 等需要耗費 CPU 的工作。若這些工作直接在主執行緒(main thread)執行,瀏覽器的 UI 會被阻塞,使用者會感受到卡頓、畫面不流暢,甚至出現「未回應」的情況。

Web Worker 正是為了解決這類問題而設計的。它允許我們把耗時的程式碼搬到 背景執行緒(background thread)中執行,主執行緒仍然可以保持對 DOM、事件與 UI 的即時回應。透過正確使用 Web Worker,我們可以在不犧牲使用者體驗的前提下,完成大量資料運算、加密/解密、圖像處理等工作。

本篇文章將從概念、實作、常見陷阱與最佳實踐,帶領讀者一步步掌握 Web Worker 的使用方式,並提供多個實用範例,協助你在實務專案中提升效能。


核心概念

1. 什麼是 Web Worker?

  • 獨立的執行環境:Worker 只擁有自己的全域作用域 (self),無法直接存取 window、document、parent 等 UI 相關物件。
  • 非同步通訊:主執行緒與 Worker 之間只能透過 訊息傳遞(postMessage / onmessage) 來交換資料,資料會被序列化(structured clone)或拷貝(transferable objects)。
  • 種類
    • Dedicated Worker:最常用的類型,僅與建立它的腳本有連結。
    • Shared Worker:多個瀏覽器上下文(tab、iframe)可以共享同一個 Worker。
    • Service Worker:主要用於離線快取與背景同步,屬於另一層面的 Worker。

重點:Web Worker 的主要目的是將 CPU 密集型任務離線化,而不是用來直接操作 DOM。

2. 建立與使用 Dedicated Worker

// main.js (主執行緒)
const worker = new Worker('worker.js');   // 建立 Worker,傳入檔案路徑

// 傳送資料給 Worker
worker.postMessage({ cmd: 'fib', n: 40 });

// 接收 Worker 回傳的結果
worker.onmessage = (event) => {
  console.log('Fibonacci result:', event.data);
};

// 若發生錯誤
worker.onerror = (err) => {
  console.error('Worker error:', err);
};
// worker.js (Worker 執行緒)
self.onmessage = (event) => {
  const { cmd, n } = event.data;
  if (cmd === 'fib') {
    const result = fibonacci(n);
    // 回傳結果給主執行緒
    self.postMessage(result);
  }
};

// 一個簡單的遞迴 Fibonacci(僅作示範,實務上建議使用迭代法)
function fibonacci(num) {
  if (num <= 1) return num;
  return fibonacci(num - 1) + fibonacci(num - 2);
}

說明

  • new Worker('worker.js') 會在背景執行緒載入 worker.js,此檔案必須 同源(或透過 CORS 設定)。
  • postMessageonmessage 是雙向通道,資料在傳遞時會自動 structured clone,不會共享記憶體。

3. Transferable Objects – 零拷貝傳遞

對於大量二進位資料(如 ArrayBufferMessagePort),使用 Transferable Objects 可以避免拷貝,直接把所有權轉移給對方。

// 主執行緒
const buffer = new ArrayBuffer(10 * 1024 * 1024); // 10 MB
worker.postMessage(buffer, [buffer]); // 第二個參數是 transfer list

console.log(buffer.byteLength); // 0,所有權已被轉移
// worker.js
self.onmessage = (e) => {
  const receivedBuffer = e.data; // 已取得所有權
  console.log(receivedBuffer.byteLength); // 10 MB
  // ... 進行計算或解碼
  // 完成後若要回傳,可再次使用 transfer
  self.postMessage(receivedBuffer, [receivedBuffer]);
};

重點:使用 Transferable Objects 時,原始物件在傳遞後會變成空的(byteLength 為 0),因此只能在一次傳遞中使用。

4. Shared Worker 的簡易範例

Shared Worker 允許多個頁面共享同一個執行緒,適合需要 跨頁面同步狀態 的情境(如即時聊天、多人協作)。

// shared-worker.js
let connections = [];

self.onconnect = (event) => {
  const port = event.ports[0];
  connections.push(port);

  port.onmessage = (e) => {
    // 廣播訊息給所有連線
    connections.forEach(p => p.postMessage(e.data));
  };
};
// main.js (任一頁面)
const sharedWorker = new SharedWorker('shared-worker.js');
sharedWorker.port.start(); // 必須啟動 port

sharedWorker.port.onmessage = (e) => {
  console.log('收到廣播:', e.data);
};

// 發送訊息給其他頁面
sharedWorker.port.postMessage('Hello from page A');

5. 使用 Promise 包裝 Worker

為了讓程式碼更易讀,我們常把 postMessage 包裝成 Promise,讓呼叫者可以使用 async/await

// worker-wrapper.js
export function runWorker(scriptUrl, payload) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(scriptUrl);
    worker.onmessage = (e) => {
      resolve(e.data);
      worker.terminate(); // 完成後釋放資源
    };
    worker.onerror = (e) => reject(e);
    worker.postMessage(payload);
  });
}
// 使用方式
import { runWorker } from './worker-wrapper.js';

async function calculatePrime(limit) {
  const result = await runWorker('prime-worker.js', { limit });
  console.log('Prime numbers:', result);
}

常見陷阱與最佳實踐

陷阱 說明 解決方案
無法存取 DOM Worker 沒有 documentwindow,直接操作 UI 會拋錯。 把 UI 相關工作留給主執行緒,透過 postMessage 讓 Worker 回傳結果再更新畫面。
過度建立 Worker 每個 Worker 都會佔用一個執行緒,過多會造成記憶體與 CPU 飽和。 重用 Worker,或使用 Pool(工作池)管理固定數量的 Worker。
資料拷貝成本 若傳遞大量陣列或物件,會觸發深拷貝,降低效能。 使用 Transferable Objects(ArrayBuffer、MessageChannel)或 structured clone 只傳遞必要資料。
錯誤處理不足 Worker 內部錯誤不會自動冒泡到主執行緒。 監聽 worker.onerror,在 Worker 程式內使用 try/catchpostMessage 錯誤資訊。
同源政策限制 Worker 必須遵守同源(CORS)規則,跨域載入會失敗。 設定正確的 CORS 標頭,或使用 blob URL 方式動態產生腳本。
記憶體泄漏 未呼叫 worker.terminate(),Worker 會持續佔用資源。 在工作完成或不再需要時 立即 terminate,或使用 self.close() 於 Worker 端自行關閉。

最佳實踐小結

  1. 只把 CPU 密集型任務交給 Worker,避免把 I/O(如 fetch)搬到 Worker,因為主執行緒已能很好處理非阻塞 I/O。
  2. 使用模組化 Workertype: "module")可直接 import ES6 模組,提升維護性。
// main.js
const worker = new Worker('math-worker.js', { type: 'module' });
  1. 建立 Worker Pool:若需同時處理多筆任務,建立固定數量的 Worker,使用佇列(queue)分配工作。
  2. 盡量使用 Transferable Objects,尤其在處理影像、音訊、二進位檔案時,可減少記憶體拷貝。
  3. 監控效能:使用 Chrome DevTools 的「Performance」與「Workers」面板,觀察每個 Worker 的 CPU 使用率,避免單一 Worker 佔用過高資源。

實際應用場景

場景 為何適合使用 Web Worker
大型陣列排序(如 10 萬筆資料) 排序演算法是 CPU 密集型,搬到 Worker 可避免 UI 卡頓。
即時影像/影片編碼(WebRTC、Canvas) 需要大量像素運算,使用 Worker 搭配 OffscreenCanvas 可在背景完成渲染。
加密/解密(AES、RSA) 密碼學演算往往耗時,Worker 可讓使用者在等待時仍能操作介面。
資料分析與視覺化(大量統計、圖表生成) 先在 Worker 完成計算,再把結果傳回主執行緒渲染圖表。
離線同步與背景下載(Progressive Web App) 下載大檔案或同步大量資料時,使用 Worker 防止 UI 失去回應。
多人協作的即時狀態同步(Shared Worker) 多個瀏覽器分頁共享同一個狀態(如聊天室訊息、遊戲分數)。

範例:使用 OffscreenCanvas 在 Worker 中渲染動畫

// main.js
const canvas = document.getElementById('myCanvas');
const offscreen = canvas.transferControlToOffscreen(); // 取得可轉移的 Canvas

const renderWorker = new Worker('render-worker.js');
renderWorker.postMessage({ canvas: offscreen }, [offscreen]); // 轉移所有權
// render-worker.js
self.onmessage = (e) => {
  const { canvas } = e.data;
  const ctx = canvas.getContext('2d');

  let angle = 0;
  function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.save();
    ctx.translate(canvas.width / 2, canvas.height / 2);
    ctx.rotate(angle);
    ctx.fillStyle = '#ff5722';
    ctx.fillRect(-50, -50, 100, 100);
    ctx.restore();

    angle += 0.02;
    requestAnimationFrame(draw);
  }
  draw();
};

此範例中,渲染工作完全在 Worker 中執行,即使畫面持續更新,主執行緒仍能保持流暢的 UI 互動。


總結

Web Worker 是前端效能優化的重要工具,透過將 CPU 密集型 任務搬到背景執行緒,我們可以:

  • 避免 UI 卡頓,提升使用者體驗。
  • 利用多核心(multi‑core)CPU,真正做到平行運算。
  • 安全地傳遞大量資料,尤其配合 Transferable Objects 可達到零拷貝。

在實作時,記得 只在 Worker 中處理計算使用訊息通道與主執行緒溝通,並遵守 同源政策適時釋放資源terminate / close)。同時,根據需求選擇 Dedicated、Shared 或 Service Worker,並結合 Worker PoolPromise 包裝 等模式,讓程式碼更易維護、效能更佳。

透過本文的概念與範例,你已具備在實務專案中 合理使用 Web Worker 的基礎。未來不論是處理大量資料、即時影像或是跨分頁協作,都能運用這項技術,讓你的 JavaScript 應用變得更快、更穩定。祝開發順利! 🚀