{T}

浏览器缓存机制

浏览器缓存是前端性能优化的重要环节。在请求资源时,浏览器会按照 Service Worker → Memory Cache → Disk Cache → Push Cache 的顺序查找缓存。本章聚焦浏览器层面的缓存机制,包括 Memory/Disk Cache 的存储规则、Service Worker 缓存策略,以及 HTTP/2 Push Cache。

浏览器缓存四层体系

从浏览器发起请求到最终展示,缓存查找遵循以下顺序:

code
浏览器请求资源
    │
    ▼
┌─────────────────┐
│ Service Worker   │── 命中 → 从 SW Cache 返回
│ Cache            │── 未命中 ↓
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Memory Cache     │── 命中 → 从内存读取(极快)
│(内存缓存)       │── 未命中 ↓
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Disk Cache       │── 命中 → 从磁盘读取
│(磁盘缓存)       │── 未命中 ↓
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Push Cache       │── 命中 → 使用推送缓存
│(HTTP/2 推送)    │── 未命中 ↓
└─────────────────┘
    │
    ▼
  发起网络请求

Memory Cache 和 Disk Cache 本质上属于 HTTP 缓存(受 Cache-Control 等首部字段控制),只是存储位置不同。

Memory Cache 与 Disk Cache

Memory Cache(内存缓存)

Memory Cache 将资源存储在内存中,读取速度极快,但进程关闭即清除(Tab 关闭 = 内存缓存清空)。

特性

  • 速度极快:直接从内存读取,无磁盘 I/O 开销
  • 生命周期短:随 Tab 关闭而清除
  • 容量有限:内存资源宝贵,存不下大文件

Disk Cache(磁盘缓存)

Disk Cache 将资源存储在磁盘中,读取速度较慢,但容量大、持久化存储。

特性

  • 容量大:可缓存更多资源
  • 持久化:Tab 关闭后仍保留
  • 速度较慢:有磁盘 I/O 开销

Memory Cache 与 Disk Cache 的选择:浏览器通常会优先命中 Memory Cache(更快),进程关闭后降级到 Disk Cache。开发者可通过 cache-controlmax-age 等指令间接影响存储位置,但不能强制指定。

Service Worker 缓存

Service Worker 可编程地控制缓存,提供更精细的缓存策略(缓存优先、网络优先等):

  • 必须注册后才能工作

生命周期

Service Worker 有严格的生命周期控制,确保站点始终可控:

code
                  ┌───────────────┐
                  │   parsed      │
                  └───────┬───────┘
                          │ Register
                          ▼
                  ┌───────────────┐
                  │  installing   │ ← install 事件触发
                  └───────┬───────┘
                          │ install 事件完成
                          ▼
                  ┌───────────────┐
                  │   installed   │ ← waiting 状态,等待旧 SW 释放控制
                  └───────┬───────┘
                          │ 旧 SW 不再控制页面
                          ▼
                  ┌───────────────┐
                  │  activating   │ ← activate 事件触发
                  └───────┬───────┘
                          │ activate 事件完成
                          ▼
                  ┌───────────────┐
                  │   activated   │ ← 功能完全可用,可拦截请求
                  └───────┬───────┘
                          │ 页面关闭后
                          ▼
                  ┌───────────────┐
                  │   redundant   │ ← 已废弃,不再使用
                  └───────────────┘

生命周期事件

事件触发时机典型用途
installSW 首次安装预缓存核心资源(App Shell)
activateSW 激活清理旧缓存、接管控制权
fetch页面发起网络请求拦截请求、自定义缓存策略
message接收主线程消息通信、更新缓存
push接收推送通知离线通知

install 事件

javascript
// 预缓存核心资源(App Shell)
const CACHE_NAME = 'v1';
const CACHE_URLS = [
  '/',
  '/index.html',
  '/styles/main.css',
  '/scripts/app.js',
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(CACHE_URLS);
    })
  );
});

activate 事件

javascript
// 清理旧版本缓存
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames
          .filter((name) => name !== CACHE_NAME)
          .map((name) => caches.delete(name))
      );
    })
  );
});

fetch 事件

javascript
// 拦截请求并返回缓存
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});

注册 Service Worker

在主线程中注册 SW,需注意几个关键点:

javascript
// 检查浏览器支持
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/sw.js', { scope: '/' })
      .then((registration) => {
        console.log('SW 注册成功:', registration.scope);
      })
      .catch((error) => {
        console.log('SW 注册失败:', error);
      });
  });
}

注意事项

  • SW 的作用域默认为其所在目录及其子目录,可通过 scope 参数调整
  • SW 脚本文件应放在站点根目录以获得最大作用域
  • 页面关闭后 SW 不会立即销毁,可继续运行处理 push、sync 等事件
  • SW 更新时会进入 waiting 状态,直到所有使用旧 SW 的页面关闭

缓存策略

Service Worker 的缓存策略决定了从缓存还是网络获取资源,常用策略如下:

Cache First(缓存优先)

优先从缓存读取,缓存未命中时回退到网络。适合不常变化的资源。

javascript
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cachedResponse) => {
      return cachedResponse || fetch(event.request);
    })
  );
});

Network First(网络优先)

优先从网络获取,网络失败时回退到缓存。适合经常更新的资源。

javascript
self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .then((response) => {
        // 网络成功,更新缓存
        const clone = response.clone();
        caches.open(CACHE_NAME).then((cache) => {
          cache.put(event.request, clone);
        });
        return response;
      })
      .catch(() => {
        // 网络失败,回退缓存
        return caches.match(event.request);
      })
  );
});

Stale While Revalidate(后台更新)

先返回缓存内容(即使过期),同时在后台发起网络请求更新缓存。下次访问时使用更新后的内容。适合对实时性要求不高但希望尽快展示的资源。

javascript
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cachedResponse) => {
      const fetchPromise = fetch(event.request).then((networkResponse) => {
        caches.open(CACHE_NAME).then((cache) => {
          cache.put(event.request, networkResponse.clone());
        });
        return networkResponse;
      });
      return cachedResponse || fetchPromise;
    })
  );
});

Cache Only(仅缓存)

只从缓存获取,不发起网络请求。适合App Shell 等静态资源。

Network Only(仅网络)

只从网络获取,不使用缓存。适合非 GET 请求或实时性要求极高的场景。

策略选型指南

策略适用资源类型特点
Cache First字体、图片、静态资源速度快,离线可用
Network FirstHTML、API 请求内容最新,离线兜底
Stale While RevalidateCSS、JS、非核心 API速度快且内容逐步更新
Cache Only预缓存的 App Shell离线可用
Network Only非 GET 请求数据实时

更新机制

Service Worker 文件的更新检测由浏览器自动完成:

  1. 字节级对比:浏览器访问 SW 文件时进行字节对比,任何变化都触发更新
  2. 安装新 SW:新 SW 进入 installing 状态
  3. 等待接管:旧 SW 控制的页面全部关闭后,新 SW 才能激活
  4. 强制更新:调用 registration.update()self.skipWaiting() + clients.claim()
javascript
// sw.js 中跳过等待
self.addEventListener('install', (event) => {
  self.skipWaiting(); // 跳过 waiting 状态,直接激活
});

self.addEventListener('activate', (event) => {
  event.waitUntil(self.clients.claim()); // 立即接管所有页面
});

注意skipWaiting() + clients.claim() 组合可能导致资源不一致(新 SW 的缓存策略与旧页面加载的资源不匹配),生产环境慎用。

Push Cache(HTTP/2 推送缓存)

Push Cache 是 HTTP/2 中新增的缓存阶段,当服务器主动推送资源时,浏览器将其暂存于 Push Cache。

特性

  • 生命周期极短:会话级别,关闭会话即释放
  • 可被覆盖:同一资源多次推送,以最后一次为准
  • 请求匹配:仅匹配完全相同的请求(URL + 请求头)
  • 优先级最低:位于缓存查找链的最末端
  • 受 HTTP 缓存首部影响:Cache-Control、max-age 等字段对推送资源同样生效

Push Cache 在实际应用中使用较少,了解其存在即可。

浏览器缓存完整流程

综合 HTTP 缓存与浏览器四层缓存,一次完整的资源请求流程如下:

code
1. 浏览器发起请求
2. 检查 Service Worker 缓存
   ├── 命中 → 返回缓存响应
   └── 未命中 ↓
3. 检查 Memory Cache
   ├── 命中 → 从内存返回
   └── 未命中 ↓
4. 检查 Disk Cache
   ├── 命中 → 检查强缓存是否过期
   │   ├── 未过期 → 200 (from disk cache) ✓
   │   └── 已过期 → 携带 ETag/Last-Modified 协商缓存
   │       ├── 304 → 从缓存读取 ✓
   │       └── 200 → 返回新资源并更新缓存
   └── 未命中 ↓
5. 检查 Push Cache(HTTP/2)
   ├── 命中 → 返回推送缓存
   └── 未命中 ↓
6. 发起网络请求