Appearance
浏览器缓存机制详解
目录
HTTP 缓存
HTTP 缓存是浏览器最基础也是最重要的缓存机制,分为强缓存和协商缓存两种。
强缓存
原理: 浏览器在请求资源时,先检查本地缓存是否过期。如果未过期,直接使用本地缓存,不发送请求到服务器。
相关 Header:
Expires:HTTP/1.0 版本,指定一个绝对过期时间Cache-Control:HTTP/1.1 版本,优先级更高max-age:相对过期时间(秒)no-cache:跳过强缓存,直接走协商缓存no-store:不缓存任何内容public:可以被所有中间代理缓存private:只能被浏览器缓存
使用场景:
- 静态资源(图片、CSS、JS)
- 不常变化的公共资源
具体案例:
http
HTTP/1.1 200 OK
Content-Type: image/jpeg
Cache-Control: public, max-age=31536000
Date: Tue, 15 Mar 2025 10:00:00 GMT这个图片资源会被缓存 1 年,期间直接使用本地缓存。
协商缓存
原理: 当强缓存过期后,浏览器携带缓存标识向服务器发送请求,服务器根据标识判断资源是否更新。
相关 Header:
- 第一组:
Last-Modified(响应头):资源最后修改时间If-Modified-Since(请求头):浏览器带上上次的 Last-Modified
- 第二组:
ETag(响应头):资源唯一标识符(通常是哈希值)If-None-Match(请求头):浏览器带上上次的 ETag
流程:
- 浏览器请求资源
- 服务器返回资源 + ETag/Last-Modified
- 下次请求时,浏览器带上 If-None-Match/If-Modified-Since
- 服务器验证:
- 资源未更新:返回 304 Not Modified,浏览器使用缓存
- 资源已更新:返回 200 + 新资源 + 新的 ETag/Last-Modified
使用场景:
- 可能会更新的资源
- 需要精确控制缓存的场景
具体案例:
javascript
// 服务端(Node.js Express)示例
app.get('/api/data', (req, res) => {
const data = { message: 'Hello' }
const etag =
'"' +
crypto.createHash('md5').update(JSON.stringify(data)).digest('hex') +
'"'
if (req.headers['if-none-match'] === etag) {
return res.status(304).end()
}
res.set('ETag', etag)
res.set('Cache-Control', 'no-cache')
res.json(data)
})Service Worker 缓存
原理: Service Worker 是独立于网页运行的脚本,可以拦截和处理网络请求,实现自定义的缓存策略。
特点:
- 独立于主线程
- 可以离线访问
- 持久化存储
- 需要 HTTPS(本地开发除外)
使用场景:
- PWA(渐进式 Web 应用)
- 离线应用
- 需要精细控制缓存策略的场景
具体案例:
javascript
// sw.js - Service Worker 文件
const CACHE_NAME = 'my-cache-v1'
const ASSETS_TO_CACHE = ['/', '/index.html', '/styles.css', '/app.js']
self.addEventListener('install', event => {
event.waitUntil(
caches
.open(CACHE_NAME)
.then(cache => cache.addAll(ASSETS_TO_CACHE))
.then(() => self.skipWaiting())
)
})
self.addEventListener('activate', event => {
event.waitUntil(
caches
.keys()
.then(cacheNames => {
return Promise.all(
cacheNames
.filter(name => name !== CACHE_NAME)
.map(name => caches.delete(name))
)
})
.then(() => self.clients.claim())
)
})
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
if (response) {
return response
}
return fetch(event.request).then(response => {
if (!response || response.status !== 200) {
return response
}
const responseToCache = response.clone()
caches
.open(CACHE_NAME)
.then(cache => cache.put(event.request, responseToCache))
return response
})
})
)
})javascript
// 注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then(reg => console.log('SW registered'))
.catch(err => console.log('SW registration failed:', err))
}Memory Cache
原理: 内存缓存,将资源存储在浏览器内存中,读取速度极快,但关闭标签页后会释放。
特点:
- 读取速度最快
- 临时存储,生命周期短
- 容量小
- 浏览器自动管理
使用场景:
- 页面内频繁访问的资源
- 临时数据
具体案例: 当你刷新页面时,很多资源会直接从 Memory Cache 读取(Chrome DevTools Network 面板中 Size 列显示 "memory cache")。
Disk Cache
原理: 磁盘缓存,将资源存储在硬盘上,读取速度较慢,但持久化存储,容量大。
特点:
- 持久化存储
- 容量大
- 读取速度比 Memory Cache 慢
- 浏览器自动管理
使用场景:
- 较大的资源文件
- 需要跨会话保留的资源
具体案例: 第一次访问网站后关闭浏览器,再次打开时,资源会从 Disk Cache 读取(Chrome DevTools Network 面板中 Size 列显示 "disk cache")。
Push Cache
原理: HTTP/2 推送缓存,当服务器使用 HTTP/2 Server Push 推送资源时,这些资源会存储在 Push Cache 中。
特点:
- 仅用于 HTTP/2 推送的资源
- 会话级缓存(关闭连接后释放)
- 只能被使用一次
- 在 Memory Cache 之前查找
使用场景:
- HTTP/2 Server Push 推送的资源
具体案例:
http
HTTP/2 200
link: </styles.css>; rel=preload; as=style
link: </app.js>; rel=preload; as=script服务器推送 CSS 和 JS 文件,这些文件会先存储在 Push Cache 中。
Cookie
原理: Cookie 是服务器发送到浏览器并保存在本地的小型文本文件,每次请求时会自动带上。
特点:
- 容量小(约 4KB)
- 每次 HTTP 请求都会自动携带
- 有过期时间控制
- 可以设置 domain 和 path 限制
- 支持 secure 和 HttpOnly 标志
相关属性:
expires:绝对过期时间max-age:相对过期时间(秒)domain:可访问 Cookie 的域名path:可访问 Cookie 的路径secure:仅在 HTTPS 下传输HttpOnly:禁止 JavaScript 访问SameSite:防止 CSRF 攻击
使用场景:
- 会话管理(登录状态)
- 用户偏好设置
- 追踪分析
具体案例:
javascript
// 设置 Cookie
document.cookie =
'username=john; expires=Fri, 31 Dec 2025 23:59:59 GMT; path=/; secure'
// 读取 Cookie
console.log(document.cookie)
// 服务端设置 Cookie(Node.js)
res.setHeader('Set-Cookie', [
'sessionid=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=86400'
])Web Storage
LocalStorage
原理: 本地存储,数据持久化保存,除非手动删除,否则永久存在。
特点:
- 容量较大(约 5-10MB)
- 持久化存储
- 仅在客户端存储,不会发送到服务器
- 同源限制
- 同步 API(会阻塞主线程)
使用场景:
- 用户偏好设置
- 离线数据
- 表单草稿自动保存
具体案例:
javascript
// 存储数据
localStorage.setItem('theme', 'dark')
localStorage.setItem('user', JSON.stringify({ name: 'John', age: 30 }))
// 读取数据
const theme = localStorage.getItem('theme')
const user = JSON.parse(localStorage.getItem('user'))
// 删除数据
localStorage.removeItem('theme')
localStorage.clear() // 清空所有SessionStorage
原理: 会话存储,数据在当前会话(标签页)关闭后清除。
特点:
- 容量较大(约 5-10MB)
- 会话级存储(关闭标签页清除)
- 仅在客户端存储
- 同源限制
- 同步 API
- 同一网站不同标签页不共享
使用场景:
- 临时表单数据
- 页面间传值
- 单次会话数据
具体案例:
javascript
// 存储表单草稿
sessionStorage.setItem(
'formDraft',
JSON.stringify({
name: 'John',
email: 'john@example.com'
})
)
// 页面跳转时恢复数据
const draft = JSON.parse(sessionStorage.getItem('formDraft'))
if (draft) {
document.getElementById('name').value = draft.name
document.getElementById('email').value = draft.email
}IndexedDB
原理: 浏览器内置的非关系型数据库,用于存储大量结构化数据。
特点:
- 容量大(通常 >50MB,甚至无限制)
- 持久化存储
- 异步 API(不阻塞主线程)
- 支持事务
- 支持索引查询
- 同源限制
使用场景:
- 大量数据存储
- 离线应用数据
- 需要复杂查询的场景
具体案例:
javascript
// 打开数据库
const request = indexedDB.open('MyDatabase', 1)
request.onupgradeneeded = event => {
const db = event.target.result
const store = db.createObjectStore('users', { keyPath: 'id' })
store.createIndex('name', 'name', { unique: false })
}
request.onsuccess = event => {
const db = event.target.result
// 添加数据
const tx = db.transaction('users', 'readwrite')
const store = tx.objectStore('users')
store.add({ id: 1, name: 'John', email: 'john@example.com' })
// 查询数据
const getRequest = store.get(1)
getRequest.onsuccess = () => console.log(getRequest.result)
// 使用索引查询
const index = store.index('name')
const indexRequest = index.get('John')
}Cache API
原理: Cache API 是 Service Worker 中用于缓存请求和响应的 API,提供了更灵活的缓存控制。
特点:
- 与 Service Worker 配合使用
- 可以缓存任意请求/响应
- 提供精细的缓存管理
- 异步 API
使用场景:
- PWA 离线缓存
- 自定义缓存策略
具体案例:
javascript
// 打开缓存
caches.open('my-cache-v1').then(cache => {
// 添加单个资源
cache.add('/styles.css')
// 添加多个资源
cache.addAll(['/', '/index.html', '/app.js'])
// 存储自定义响应
const response = new Response('Hello World', {
headers: { 'Content-Type': 'text/plain' }
})
cache.put('/custom', response)
// 匹配请求
cache.match('/styles.css').then(response => {
if (response) {
console.log('Cache hit!')
}
})
// 删除缓存
cache.delete('/styles.css')
})
// 获取所有缓存名称
caches.keys().then(names => console.log(names))
// 删除缓存
caches.delete('old-cache')各缓存类型对比
| 缓存类型 | 容量 | 生命周期 | 读取速度 | 存储位置 | 自动发送请求 | 主要用途 |
|---|---|---|---|---|---|---|
| HTTP 强缓存 | 取决于磁盘 | 由 Header 控制 | 快 | 磁盘/内存 | - | 静态资源 |
| HTTP 协商缓存 | 取决于磁盘 | 由 Header 控制 | 中 | 磁盘/内存 | 是 | 可能更新的资源 |
| Service Worker | 取决于磁盘 | 持久化 | 中 | 磁盘 | - | PWA、离线应用 |
| Memory Cache | 小 | 关闭标签页 | 极快 | 内存 | - | 临时资源 |
| Disk Cache | 大 | 持久化 | 慢 | 磁盘 | - | 大文件资源 |
| Push Cache | 小 | 关闭连接 | 快 | 内存 | - | HTTP/2 推送 |
| Cookie | ~4KB | 可设置 | 快 | 磁盘 | 是 | 会话、追踪 |
| LocalStorage | ~5MB | 持久化 | 中 | 磁盘 | 否 | 用户设置、离线数据 |
| SessionStorage | ~5MB | 关闭标签页 | 中 | 内存 | 否 | 临时数据 |
| IndexedDB | 很大 | 持久化 | 中 | 磁盘 | 否 | 大量数据、离线应用 |
| Cache API | 大 | 持久化 | 中 | 磁盘 | - | PWA 缓存 |
缓存查找优先级(浏览器请求资源时):
- Service Worker Cache - 由 Service Worker 控制
- Memory Cache - 内存缓存
- Push Cache - HTTP/2 推送缓存
- Disk Cache - 磁盘缓存(HTTP 缓存)
- 网络请求 - 请求服务器
最佳实践
- 静态资源:使用 HTTP 强缓存(
max-age+ 指纹哈希) - HTML 页面:使用协商缓存(
no-cache+ ETag) - 用户设置:使用 LocalStorage
- 临时数据:使用 SessionStorage
- 大量数据:使用 IndexedDB
- 离线应用:使用 Service Worker + Cache API
- 会话管理:使用 HttpOnly Cookie