浏览器缓存的多层架构
浏览器缓存不是单一的一层,而是由多个层次组成:
请求 → Service Worker 缓存
↓ 未命中
→ 内存缓存(Memory Cache)
↓ 未命中
→ 磁盘缓存(Disk Cache)
↓ 未命中
→ HTTP 缓存
↓ 未命中
→ 网络请求
内存缓存
内存缓存存储在浏览器进程的内存中。
特点:
- 读取速度极快
- 容量有限(通常几十 MB)
- 标签页关闭后消失
- 浏览器自动管理,开发者无法直接控制
什么进入内存缓存:
- 小文件(通常 < 50KB)
- 预读取的资源
- 已经解码的图片
磁盘缓存
磁盘缓存存储在文件系统中。
特点:
- 容量大(几百 MB 到几 GB)
- 持久化,标签页关闭后仍然存在
- 读取速度比内存慢
什么进入磁盘缓存:
- 大文件
- JavaScript、CSS、图片等
Service Worker 缓存
Service Worker 是一种在浏览器和服务器之间的代理,可以完全控制缓存策略:
// sw.js
const CACHE_NAME = 'v1';
const urlsToCache = ['/', '/styles.css', '/script.js'];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
// 缓存命中返回缓存,否则发起网络请求
return response || fetch(event.request);
})
);
});
HTTP 缓存
HTTP 缓存是最常用的缓存机制,通过 HTTP Header 控制:
缓存策略
| 策略 | 适用资源 | 特点 |
|---|---|---|
| 强缓存 | 不经常变化的静态资源 | 不发请求,直接用缓存 |
| 协商缓存 | 可能变化的资源 | 发请求验证是否过期 |
| 不缓存 | 每次都要最新 | 总是发起请求 |
强缓存
# Expires(HTTP/1.0,已过时)
Expires: Wed, 21 Oct 2025 07:28:00 GMT
# Cache-Control(HTTP/1.1)
Cache-Control: public, max-age=3600
# public: 可被 CDN 和浏览器缓存
# max-age: 缓存有效期(秒)
协商缓存
# Last-Modified / If-Modified-Since
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
# ETag / If-None-Match(更精确)
ETag: "33a64df551425fcc55e4d42a148795d9"
缓存决策流程
请求发起
↓
检查 Service Worker 缓存
↓ 未命中
检查内存缓存
↓ 未命中
检查磁盘缓存
↓ 未命中
检查 HTTP 缓存(强缓存)
↓ 未过期 → 直接用缓存
↓ 已过期 → 发送请求,带上协商缓存的 ETag/Last-Modified
↓ 服务器返回 304 → 用缓存
↓ 服务器返回 200 + 新内容 → 用新内容,更新缓存
实际缓存策略设计
策略一:长缓存 + 版本化(最佳)
# 文件名带 hash,内容变化时 URL 变化
/app.abc123.js
/style.css?v=2.1.0
# HTTP Header
Cache-Control: public, max-age=31536000, immutable
策略二:HTML 采用协商缓存
# HTML 变化频繁,但需要确保用户拿到最新版本
Cache-Control: no-cache
ETag: "abc123"
策略三:API 数据不缓存
# API 数据实时性要求高,不缓存
Cache-Control: no-store
这一章想说的
浏览器缓存是多层次的:
- Service Worker:完全控制缓存策略,适合离线应用
- 内存缓存:快速但短暂,适合小文件和预读取
- 磁盘缓存:持久但较慢,适合大文件
- HTTP 缓存:服务端控制,适用于大多数场景
最佳实践:长缓存 + 版本化——文件名包含 hash,内容变化时 URL 变化,自然避免缓存失效问题。