original post2026-08-15
Haru Li
Frontend Developer
14 posts
·2 min read效能瀏覽器
那個轉圈停住的瞬間,就是使用者眼中的當機
效能問題裡最難用數字說明的一種是「頁面沒有壞,只是不動了」。耗時 800 ms 聽起來還好,但那 800 ms 裡點什麼都沒反應,感覺就是當機。
所以這個 demo 我沒有從數字下手,從一個轉圈下手。
發想:讓「卡住」看得見
一樣的運算,一邊跑在主執行緒、一邊跑在 Web Worker。運算量、結果完全相同,唯一的差別是跑在哪條執行緒上。
畫面上方放一個一直在轉的圈。它是純 CSS 動畫,理論上跟你的按鈕無關。
測試:盯著那個圈
按「在主執行緒算」——轉圈整個凍住,直到運算結束才恢復。
按「在 Worker 算」——轉圈一路轉不停,運算照樣完成,耗時跟上面差不多。
兩邊都會顯示實際耗時與質數個數,證明做的是同一件事。
理解:CSS 動畫為什麼也會停
「CSS 動畫跑在合成執行緒,不會被 JS 影響」——這句話只對了一半。
transform 和 opacity 的動畫確實可以交給合成執行緒。但動畫的啟動、狀態更新、以及任何需要主執行緒參與的環節仍然會被卡住。更關鍵的是:主執行緒被佔住時,瀏覽器沒辦法處理任何事件,你的點擊、捲動、輸入全都排在後面等。
JavaScript 是單執行緒的。那段質數運算跑起來的時候,主執行緒沒有任何餘裕去做別的事——它不是「比較慢」,是完全沒空。
Web Worker 是另一條真正獨立的執行緒。主執行緒把工作丟過去,自己繼續處理事件與畫面,等 Worker 算完再用訊息把結果送回來。
const worker = new Worker(new URL("./primes.worker.ts", import.meta.url), {
type: "module",
});
worker.postMessage({ limit });
worker.onmessage = (e) => setResult(e.data);
講解:代價與界線
Worker 不是免費的:
- 沒有 DOM。 Worker 裡碰不到
document,只能算完把結果傳回來。 - 資料要複製。 傳過去的東西會被結構化複製,傳大量資料本身就有成本(可以用 Transferable 物件避開,但更麻煩)。
- 啟動有開銷。 為了一個 5 ms 的運算開 Worker,得不償失。
判準是:這段運算會不會讓主執行緒停超過一幀(約 16 ms)? 會的話才值得考慮搬走。
典型適合的場景:大量資料的解析與轉換、影像處理、加解密、複雜的搜尋或排序。
這個 demo 可以在 效能實驗室 自己操作。
