JavaScript Service Worker
Service Worker 是網頁開發中最具革命性的技術之一。它是一段運行在瀏覽器背景、獨立於網頁執行緒的腳本。它是實現 PWA (Progressive Web Apps) 的核心技術。
與 Web Worker 不同,Service Worker 的生命週期與網頁無關,即使網頁關閉,Service Worker 依然可以被喚醒(例如處理推送通知)。
Service Worker 的核心能力
- 離線瀏覽 (Offline Support):透過攔截網路請求,即便在無網路環境下也能回傳快取內容。
- 精準快取控制:比瀏覽器預設快取更靈活地管理資源更新。
- 背景任務:支援推送通知 (Push Notifications) 與背景同步 (Background Sync)。
http://localhost 是唯一的例外。生命周期 (Lifecycle) 與進階控制
Service Worker 的生命週期旨在確保網站內容的一致性。當你更新了 Service Worker 檔案,瀏覽器會偵測到差異並啟動新的生命週期。
1. 註冊 (Registration)
在主執行緒中註冊 Service Worker。
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then((reg) => console.log('SW 註冊成功'))
.catch((err) => console.log('SW 註冊失敗'));
}
2. 安裝 (Installation) 與 skipWaiting()
觸發 install 事件。通常在此快取靜態資源。新版 SW 安裝後會進入「等待中」(Waiting) 狀態,直到舊版 SW 的所有分頁都被關閉。
如果你希望新版 SW 立即生效,可以呼叫 self.skipWaiting()。
// sw.js
self.addEventListener('install', (event) => {
event.waitUntil(
caches
.open('v2')
.then((cache) => {
return cache.addAll(['/', '/main.css']);
})
.then(() => self.skipWaiting()) // 跳過等待,立即準備啟用
);
});
3. 啟用 (Activation) 與 clients.claim()
當舊版 SW 結束工作,新版便會進入 activate 階段。這裡是清理舊版本快取的好時機。呼叫 self.clients.claim() 可以讓新 SW 立即接管現有的所有分頁。
self.addEventListener('activate', (event) => {
event.waitUntil(
Promise.all([
// 清理舊快取
caches.keys().then((keys) => {
return Promise.all(
keys.map((key) => {
if (key !== 'v2') return caches.delete(key);
})
);
}),
self.clients.claim(), // 立即接管受控分頁
])
);
});
常見的快取策略 (Caching Strategies)
在 fetch 事件中,你可以實作不同的策略來應對各種場景:
離線優先 (Cache First)
適用於不常變動的靜態資源(圖片、字型)。
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
網路優先 (Network First)
適用於需要即時性的 API 資料。如果網路斷線,才回傳快取中的舊資料。
self.addEventListener('fetch', (event) => {
event.respondWith(fetch(event.request).catch(() => caches.match(event.request)));
});
Stale-While-Revalidate (過期前驗證)
這是最高級的策略:先立即回傳快取的內容(速度最快),同時在背景私下發送網路請求更新快取。下次開啟時就會看到最新內容。
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('v2').then((cache) => {
return cache.match(event.request).then((response) => {
const fetchPromise = fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
return response || fetchPromise;
});
})
);
});
實務:提示使用者有新版本
雖然 skipWaiting() 很有用,但有時直接重新整理頁面會造成使用者資料遺失。更好的做法是在前端彈出提示:
// main.js
navigator.serviceWorker.register('/sw.js').then((reg) => {
reg.addEventListener('updatefound', () => {
const newWorker = reg.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// 發現新版 SW,跳出「有新版本,請重新整理」的提示框
if (confirm('有可用的新版本,是否立即更新?')) {
window.location.reload();
}
}
});
});
});
如何進行偵錯 (Debugging)
在 Chrome 瀏覽器中,你可以透過以下方式測試 Service Worker:
- 開啟 DevTools > Application 頁籤。
- 點擊 Service Workers 側選單。
- Offline 勾選框:模擬斷網環境,測試快取是否生效。
- Update on reload:開發時勾選此項,每次重新整理都會強迫更新 Service Worker。
- Console:在
sw.js裡的console.log會出現在獨立的調試視窗,需點擊「inspect」開啟。
總結
Service Worker 不只是快取代理,它是網頁通往原生應用體驗的門票。透過靈活的生命週期控制與快取策略,你可以建立極速載入且具備強大韌性的現代化網頁。