「HTML5 快取」通常指過往的應用程式快取(Application Cache),也稱為 AppCache。
它允許網站列出瀏覽器應儲存以供離線使用的資源。
AppCache 已過時,並已從現代瀏覽器移除。新網站不要使用它。
現代應用程式通常使用:
- HTTP 快取:供一般資源重用。
- Service Worker 與 Cache API:實作由應用程式控制的離線行為。
這些機制用途不同,不能互相替代。
38.1 過往的應用程式快取
AppCache 使用根元素 <html> 上的 manifest 屬性:
<!-- 只供了解歷史語法。新應用程式不要使用。 -->
<html lang="zh-Hant-HK" manifest="site.appcache">所參照的檔案描述應快取哪些資源。
過往的快取清單可能包含:
CACHE MANIFEST
# Version 1
CACHE:
index.html
styles.css
app.js
NETWORK:
*
FALLBACK:
/ /offline.html這個例子用來解釋舊資料,不是適用於現代瀏覽器的做法。
38.2 過往的清單區段
| 區段 | 過往用途 |
|---|---|
CACHE: | 列出供離線儲存的資源 |
NETWORK: | 列出需要網絡存取的請求 |
FALLBACK: | 將 URL 命名空間對應至後備資源 |
清單以以下內容開始:
CACHE MANIFEST通常使用以下回應標頭傳送:
Content-Type: text/cache-manifest修改清單會觸發過往的更新流程。更新註解是常見的做法,用來表示新的快取版本。
AppCache 的生命週期及請求處理規則造成不少難以理解的行為,包括網頁意外維持舊版本,以及更新失敗後難以復原。因此,它被其他機制取代,而不是繼續改良。
38.3 AppCache 不是網頁應用程式資訊清單
過往的快取清單與現代的**網頁應用程式資訊清單(web app manifest)**不同:
<link rel="manifest" href="/manifest.webmanifest">網頁應用程式資訊清單提供應用程式中繼資料,例如:
- 應用程式名稱。
- 圖示。
- 啟動 URL。
- 顯示偏好。
它本身不會快取應用程式,也不會令應用程式支援離線使用。
不要混淆:
| 功能 | 用途 |
|---|---|
過往的 <html manifest="…"> | AppCache 設定 |
<link rel="manifest" href="…"> | 網頁應用程式中繼資料 |
| Service Worker | 可程式化的請求處理及其他受支援背景功能 |
| Cache API | 由程式碼管理的請求與回應配對儲存 |
38.4 一般 HTTP 快取
HTTP 快取讓瀏覽器及中介快取按照 HTTP 規則重用回應。
伺服器透過回應標頭控制大部分行為:
Cache-Control: public, max-age=3600這允許適用的快取將回應視為在一小時內保持新鮮。
常見指令包括:
| 指令 | 一般意思 |
|---|---|
max-age | 設定新鮮度有效期 |
no-cache | 重用前必須驗證 |
no-store | 指示快取不要儲存回應 |
private | 阻止共用快取儲存回應 |
immutable | 表示回應在新鮮度有效期內不會改變 |
no-cache 不代表「不要儲存」。 如果必須禁止儲存,應使用 no-store。
ETag 和 Last-Modified 等驗證資訊可以支援條件式請求。如果已儲存的回應仍然有效,伺服器可能返回 304 Not Modified。
應透過真正的 HTTP 回應標頭設定快取,而不是使用聲稱能設定快取的 meta 標籤。
38.5 Service Worker
Service Worker 是獨立於網頁執行的程式碼,可以處理受它控制的用戶端所發出的請求。
從網頁註冊:
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js")
.catch((error) => {
console.error("Service Worker 註冊失敗:", error);
});
}重要要求及限制包括:
- Service Worker 需要安全環境,通常是 HTTPS。
- 一般支援在
localhost進行本機開發。 - Worker 程式碼必須從相同來源提供。
- 作用範圍決定它可以控制哪些網頁。
- 註冊不代表立即控制目前網頁。
- Worker 不會持續執行;瀏覽器管理其存活時間。
位於 /sw.js 的 Worker,預設作用範圍通常是 /。位於子目錄的 Worker,預設作用範圍通常較窄。
只有在應用程式需要相關能力時,才使用 Service Worker。
38.6 Cache API
Cache API 在具名快取中儲存請求與回應配對:
const cache = await caches.open("course-assets-v1");
await cache.addAll([
"/offline.html",
"/styles.css"
]);這些陳述必須在適當的非同步環境中執行。
Cache API 可用於 Service Worker,以及支援它的安全網頁環境。單純儲存項目不需要 Service Worker,但通常會配合它,在離線請求時提供快取內容。
重要區別包括:
- Cache API 儲存與一般 HTTP 快取分開。
- 已儲存的回應不會自動用於網頁請求。
- Cache API 項目不會根據 HTTP 新鮮度標頭自動到期。
- 應用程式必須管理更新及刪除。
不要以為帶有 Cache-Control: no-store 的回應,會自動被明確的 Cache API 儲存邏輯拒絕。應刻意避免儲存敏感回應。
38.7 常見請求策略
Service Worker 可以為不同資源選擇不同策略。
| 策略 | 做法 | 常見用途 |
|---|---|---|
| 快取優先(cache first) | 使用快取回應;沒有時才取得網絡資源 | 具有版本識別的靜態資源 |
| 網絡優先(network first) | 先嘗試網絡;失敗時使用快取內容 | 重視最新內容的網頁 |
| 先用舊快取再更新(stale while revalidate) | 返回快取內容,同時在背景更新 | 可以接受部分過時內容的資源 |
| 只用網絡(network only) | 不使用應用程式管理的快取回應 | 敏感或必須保持最新的請求 |
這裏的「只用網絡」,表示略過應用程式的 Cache API 策略。除非請求及回應規則另有設定,瀏覽器的一般 HTTP 快取仍然可以生效。
沒有一種策略適合所有資源。
應考慮:
- 可以接受內容過時多久。
- 離線存取是否有用。
- 回應是否個人化。
- 如何呈現錯誤。
- 如何向使用者提供更新。
38.8 生命週期與更新
Service Worker 的生命週期包括安裝、等待及啟用。
安裝期間,應用程式可以準備必要的快取資源。
如果舊 Worker 仍然控制已開啟網頁,新 Worker 可能會等待。這有助避免在使用期間意外改變應用程式的請求行為。
啟用期間,應用程式通常會移除過時快取。
skipWaiting() 和 clients.claim() 等方法可以加快採用新 Worker,但沒有更新計劃就使用它們,可能令舊網頁配合新的資源處理行為。
應將更新視為應用程式設計的一部分,而不是事後補充。
一致地管理資源及快取版本,並在有需要時提供適當的重新載入或更新體驗。
38.9 完整範例:離線後備 Worker
以下可重用 Worker 嘗試正常的網頁導覽請求,並在網絡請求失敗時提供已快取的離線頁面。
它刻意不會快取已瀏覽網頁、需要身分驗證的回應、API 結果或其他靜態資源。
註冊 Worker 前,應先建立獨立完整的 /offline.html。必要樣式應放在頁面內,或另外加入安裝快取。
id: hong-kong-html-cache-service-worker
name: HTML5 快取替代方案 — 離線後備 Worker
type: code.javascript
content: |-
// 儲存為 /sw.js,並透過 HTTPS 或 localhost 提供。
// 註冊這個 Worker 前,先建立 /offline.html。
const CACHE_NAME = "course-offline-v1";
const CACHE_PREFIX = "course-offline-";
const OFFLINE_URL = "/offline.html";
self.addEventListener("install", (event) => {
event.waitUntil(
(async () => {
const cache = await caches.open(CACHE_NAME);
await cache.add(
new Request(OFFLINE_URL, { cache: "reload" })
);
})()
);
});
self.addEventListener("activate", (event) => {
event.waitUntil(
(async () => {
const names = await caches.keys();
await Promise.all(
names
.filter((name) =>
name.startsWith(CACHE_PREFIX) &&
name !== CACHE_NAME
)
.map((name) => caches.delete(name))
);
})()
);
});
self.addEventListener("fetch", (event) => {
const request = event.request;
const url = new URL(request.url);
if (
request.method !== "GET" ||
request.mode !== "navigate" ||
url.origin !== self.location.origin
) {
return;
}
event.respondWith(
(async () => {
try {
return await fetch(request);
} catch {
const cache = await caches.open(CACHE_NAME);
const fallback = await cache.match(OFFLINE_URL);
if (fallback) {
return fallback;
}
return new Response(
"無法離線開啟此頁面。請重新連線後再試。",
{
status: 503,
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "no-store"
}
}
);
}
})()
);
});使用第 38.5 節的程式碼註冊它。
在這個範例中:
- 安裝時儲存離線頁面。
- 預先快取失敗會令安裝無法成功。
- 啟用時只刪除使用本應用程式前綴的舊快取。
- 處理同來源的 GET 導覽請求。
- 其他請求保留正常行為。
- 網絡失敗時提供離線後備內容。
404和500等 HTTP 錯誤回應會正常返回,因為fetch()不會只因這些狀態而拒絕。- Worker 不會強制立即啟用,也不會主動接管現有網頁。
註冊後,第一個網頁可能尚未受控。Worker 啟用後,通常需要再次導覽或重新載入。
這是離線後備方案,不是完整離線應用程式。請求也可能等待一段時間後才失敗;正式應用程式可能需要明確的逾時策略。
38.10 儲存、私隱與復原
瀏覽器管理的儲存不是永久的。
快取資料可能因以下原因遺失:
- 儲存空間不足而被清除。
- 使用者操作。
- 私密瀏覽限制。
- 瀏覽器政策。
- 應用程式清理。
不要將快取作為重要使用者資料的唯一副本。
應特別小心處理:
- 帳戶專用網頁。
- 個人資訊。
- 依賴身分驗證的回應。
- 登出後應消失的資料。
同來源快取不會自動按目前使用應用程式的帳戶分開。應設計適當儲存及清理方式,避免某個帳戶收到另一個帳戶的快取內容。
取消註冊 Service Worker,也不會自動刪除其 Cache API 項目。
應為過時或有問題的 Worker 提供復原計劃,並避免使用會刪除同來源其他應用程式快取的全面清理程式碼。
38.11 測試離線行為
不要只測試一次成功的離線載入。
應檢查:
- 沒有任何快取資源時的首次瀏覽。
- 缺少必要資源時的安裝。
- Worker 啟用後重新載入。
- 離線時的導覽。
- 緩慢或不穩定的連線。
- HTTP 錯誤回應。
- 部署新 Worker 版本。
- 多個開啟中的分頁。
- 儲存被清除或因空間不足而移除。
- 適用情況下的登出及帳戶切換。
瀏覽器開發工具通常提供面板,讓你檢查 Service Worker 及快取儲存。
開發工具設定可能改變正常生命週期,因此也應在沒有強制更新選項的情況下測試。
38.12 常見錯誤
| 錯誤 | 較好的做法 |
|---|---|
使用過往的 manifest 屬性 | 以受支援機制取代 AppCache |
| 以為網頁應用程式資訊清單會啟用離線功能 | 另外實作快取 |
| 將 HTTP 快取與 Cache API 儲存視為相同機制 | 理解各自規則 |
以為 no-cache 禁止儲存 | 使用適當指令 |
| 不加區分地快取所有回應 | 根據新鮮度及私隱需要選擇資源 |
| 期望快取項目自動到期 | 實作快取維護政策 |
| 以為註冊後立即控制網頁 | 考慮生命週期及作用範圍 |
| 未協調網頁就強制更新 Worker | 設計安全更新流程 |
期望 fetch() 對所有 HTTP 錯誤都拒絕 | 按需要檢查狀態 |
| 將快取儲存視為永久 | 處理資料清除及復原 |
| 以為登出會自動清除快取資料 | 實作按帳戶管理的清理 |
| 刪除同來源的所有快取 | 只刪除本應用程式擁有的快取 |
本章總結: 過往的 HTML5 應用程式快取已經過時。一般資源重用應使用 HTTP 快取;需要刻意設計的離線行為時,使用 Service Worker 與 Cache API。應將新鮮度、更新、儲存遺失、私隱及失敗復原納入實作計劃。















