Skip to content

浏览器缓存机制详解

目录


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

流程:

  1. 浏览器请求资源
  2. 服务器返回资源 + ETag/Last-Modified
  3. 下次请求时,浏览器带上 If-None-Match/If-Modified-Since
  4. 服务器验证:
    • 资源未更新:返回 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 是服务器发送到浏览器并保存在本地的小型文本文件,每次请求时会自动带上。

特点:

  • 容量小(约 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 缓存

缓存查找优先级(浏览器请求资源时):

  1. Service Worker Cache - 由 Service Worker 控制
  2. Memory Cache - 内存缓存
  3. Push Cache - HTTP/2 推送缓存
  4. Disk Cache - 磁盘缓存(HTTP 缓存)
  5. 网络请求 - 请求服务器

最佳实践

  1. 静态资源:使用 HTTP 强缓存(max-age + 指纹哈希)
  2. HTML 页面:使用协商缓存(no-cache + ETag)
  3. 用户设置:使用 LocalStorage
  4. 临时数据:使用 SessionStorage
  5. 大量数据:使用 IndexedDB
  6. 离线应用:使用 Service Worker + Cache API
  7. 会话管理:使用 HttpOnly Cookie

基于 VitePress 的本地知识库