iT邦幫忙

2025 iThome 鐵人賽

DAY 24
3
Modern Web

從 Canvas 到各式各樣的 Web API 之旅系列 第 24

Day 24 - 從頭瞭解 Service Worker

  • 分享至 

  • xImage
  •  

想讓網站在沒網路也能開、重複資源不再每次都下載、頁面秒開又省流量?這些日常體驗的升級,靠的就是 Service Worker。它像「瀏覽器內建的代理層」,能攔截請求、回傳快取、再視情況呼叫網路,讓你的網站具備:

  • 離線可用(沒網路也讀得到已快取頁面/資料)
  • 快取加速(常用 JS/CSS/圖片直接本地命中)
  • 智慧回應(先給快取,背景再更新,不卡畫面)
  • 背景能力(推播、背景同步等後續延伸)

很棒吧!就讓我們看下去~ 🛜


為什麼要用 Service Worker?

傳統網頁一關就消失,沒辦法在背景持續工作,也無法在離線時回應請求。Service Worker 改變了這件事:

  1. 攔截請求:像代理一樣,站在「瀏覽器 ↔ 網路」之間,決定要走網路或回快取。
  2. 離線可用:即使沒網路,也能回傳先前快取的頁面或靜態資源。
  3. 體驗加速:常用資源不必每次都打網路,直接從本地快取讀取。
  4. 背景能力:推播、背景同步,即使網頁關掉也能運作。

心智模型:把它想像成「瀏覽器內建的反向代理 + 小型背景服務」。


Service Worker 的核心特性

1. 獨立執行緒(Worker Thread)

  • 和一般的 JavaScript 程式不同,Service Worker 不在主執行緒(Main Thread)上執行,而是在另一條獨立的執行緒(Worker Thread)上執行
  • 不會阻塞 UI 渲染
  • 即使網頁關閉,Service Worker 依然可以在需要時被喚醒

Service Worker

2. 與頁面隔離

  • 無法直接存取 DOM,沒有 windowdocument
  • 需要與頁面通訊時,透過 postMessage 進行

3. 事件驅動、按需喚醒

  • 獨立存在,註冊後會常駐在「瀏覽器的後台環境」,不隸屬於任何特定的頁面,像是一個背景守護程式
  • 收到請求、推播等事件才被喚醒(即使使用者把該網站的分頁全部關掉)
  • 平時會被瀏覽器終止以節省資源

4. 像 Proxy 一樣,能夠攔截和處理對伺服器的網絡請求

  • 所有從頁面發出的 fetch 請求,都會先經過 Service Worker
  • 有權決定:
    • 直接回快取(Cache First → 離線或加速)
    • 打 API 再回應(Network First → 確保最新資料)
    • 快取 + 網路混合策略(Stale-While-Revalidate → 先回快取,再偷偷更新)
  • 換句話說,Service Worker 就是你網站的「小型反向代理伺服器」

Service Worker

5. 必須在 HTTPS 下運行

  • 除了 localhost 例外
  • 確保傳輸安全性,避免中間人攻擊

Service Worker 的生命週期

跟頁面獨立,有自己完整的流程:

  1. 註冊(register):在入口 JS 呼叫 navigator.serviceWorker.register(),瀏覽器會檢查是否已有相同的 Service Worker,並偵測檔案是否有更新。
  2. 安裝(install):監聽 install 事件,首次註冊 Service Worker 或者 sw.js(Service Worker 腳本)更改 時觸發,常用來快取必要資源。
  3. 啟用(activate):監聽 activate 事件,常用來清理舊的快取或執行資料庫遷移等操作。
    在以下時機觸發:
    • 首次安裝後:install 完成後會立即 activate
    • 更新版本後:新版 SW install 完成後,會進入 waiting 狀態,等待所有使用舊版的頁面關閉後才 activate
  4. 攔截(fetch):攔截頁面所有網路請求(Service Worker 的「類 Proxy」能力),可以選擇回快取或打 API。

限制:

  • 路徑(scope) 影響攔截範圍,預設為 sw.js 所在目錄及其子目錄。
  • Service Worker 是事件驅動的,可能會被瀏覽器終止以節省資源,不應依賴全域狀態。

註冊 Service Worker

首先在根目錄建立 sw.js(Service Worker 腳本),然後註冊 Service Worker。

在頁面 JS:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(() => console.log('✅ Service Worker 註冊成功'))
    .catch(err => console.error('❌ 註冊失敗:', err));
}

Service Worker


安裝 Service Worker

常見的用途是預快取資源。

sw.js

const CACHE_NAME = 'my-cache-v1';
const ASSETS = ['/index.html', '/style.css', '/script.js'];

self.addEventListener('install', (event) => {
  console.log('📦 Service Worker 安裝');
    
  // 安裝完成前先把必要資源寫入快取;waitUntil 會延後 install 結束直到 Promise 完成
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS))
  );
});

event.waitUntil() 會延遲安裝完成,直到快取寫入完畢。


啟用 Service Worker

install 完成後,新版 SW 會嘗試進入 activate。若是第一次安裝,會馬上啟用;若是更新,則需要等舊版頁面全部關閉後才會 activate。
常見的用途是刪除舊版本的快取,避免佔用空間或讀到舊檔案。

sw.js

self.addEventListener('activate', (event) => {
  console.log('🚀 Service Worker 啟用');
    
  // 在啟用時清理舊版快取:避免讀到過期資源、釋放空間
  event.waitUntil(
    caches.keys().then(keys => {
      return Promise.all(
        keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))
      );
    })
  );
});

Fetch:攔截請求,回應快取

最常見的邏輯是「快取優先,沒有再打網路」,這樣當使用者沒網路時,仍然可以讀到快取的頁面或資源。

sw.js

self.addEventListener('fetch', (event) => {
  event.respondWith(
    // 先查快取:若已有對應的 Response,直接回快取(Cache First)
    caches.match(event.request).then(cached => {
      return cached || fetch(event.request);
    })
  );
});

⭐️ 小測驗:Service Worker 生命週期情境

這部分有點複雜,很容易搞混,來考驗一下大家對生命週期的理解,也幫助釐清觀念。

Q:Service Worker 生命週期中的 register、install、activate,請回答以下情境,會執行到哪些?

1. 第一次上線網站

  • 流程:register → install → activate
  • 解釋:第一次載入網站,瀏覽器偵測到有 sw.js,會先 註冊(register);因為沒有舊版本,所以馬上觸發 安裝(install),將資源快取好;接著執行 啟用(activate),正式接管網站。

2. 重新整理網站

  • 流程:register
  • 解釋:重新整理網頁時,Service Worker 已經存在了,不會重新安裝或啟用。只會再次執行 註冊(register) 檢查 SW 狀態(檢查是否有新版本)。

3. 關閉該網站頁面,再另開網站頁面

  • 流程:register
  • 解釋:Service Worker 註冊後會持續存在於瀏覽器中。關閉頁面後,SW 進入閒置狀態但不會被移除。重新開啟頁面時,只會執行 註冊(register) 檢查 SW 狀態,並喚醒 SW 繼續控制頁面。不會重新 install 或 activate

4. sw.js 檔案異動

  • 流程:register → install → (waiting) → activate
  • 解釋:瀏覽器偵測到 sw.js 的檔案有更新,會先 註冊(register) 並觸發新的 安裝(install)。舊的 SW 還在控制頁面,新的 SW 會進入 waiting 狀態,等待所有使用舊版 SW 的頁面關閉後才進入 啟用(activate)。一旦啟用成功,新版 SW 接管控制權,舊版就會被清除。這就是「版本更新」的典型流程。

圖示:sw.js 檔案異動後,等待所有使用舊版 SW 的頁面關閉後才進入 啟用(activate)
Service Worker
Service Worker

小結:

  • register:幾乎每次開網頁都會執行,用來檢查 SW 狀態和是否有新版本。
  • install:只在「第一次註冊」或「SW 檔案有變動」時執行。
  • activate:在「第一次安裝後」或「SW 更新後(所有舊頁面關閉時)」執行。

這樣一比較,就能清楚知道什麼情境下會觸發哪些事件了!
大家應該有比較清楚 Service Worker 生命週期了吧~ 😇


Cache API 簡介

Cache API 是專為 Service Worker 設計的儲存機制,用於快取網路請求和回應。透過全域的 caches 物件,可以輕鬆管理多個快取空間。

  • caches.open(name) → 開啟/建立快取空間。
  • cache.addAll(urls) → 一次加入多個資源,內部會逐一發送 fetch 請求。任一資源失敗(404、網路錯誤等)會導致整個 Promise reject。正式專案建議改用逐筆 cache.put(),提高容錯能力。
  • caches.match(request) → 查詢快取。
  • cache.put(request, response) → 手動新增快取。
  • caches.delete(name) → 刪除快取空間。

這比 localStorage / IndexedDB 更適合做「網路資源」快取。


小結

今天理解了 Service Worker 的基礎:

  • 它跑在 獨立執行緒,不會阻塞主執行緒,也不直接碰 DOM。
  • 具備類 Proxy 的特性:站在瀏覽器與網路之間攔截請求,決定回快取或打網路,甚至混合策略(SWR)。
  • 生命週期分為 installactivatefetch
  • 可以用 Cache API 快取與回應資源,實現基本的離線體驗。

明天我們會更進一步探討 快取策略(Cache First、Network First、Stale-While-Revalidate)。🔥


👉 歡迎追蹤這個系列,我會從 Canvas 開始,一步步帶你認識更多 Web API 🎯


上一篇
Day 23 - IndexedDB 進階篇:索引建立、Cursor 遍歷、範圍查詢
下一篇
Day 25 - Service Worker 快取策略與更新陷阱
系列文
從 Canvas 到各式各樣的 Web API 之旅25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言