HTTP缓存从强缓存到协商缓存,前端性能优化第一步

4 阅读3分钟

页面加载慢,第一反应是优化代码、压缩资源。但还有一个更容易忽略的点——HTTP缓存。

配置得当的话,二次访问几乎不用发请求,资源直接从本地拿。今天把HTTP缓存的完整机制讲清楚。

强缓存:直接从本地拿,不问服务器

强缓存的意思是:浏览器第一次请求资源后,服务器会告诉浏览器"这个资源你可以缓存多久"。在这个时间内,浏览器再次需要这个资源时,直接从本地读取,不发任何请求。

控制强缓存的两个HTTP头:

HTTP头出现版本优先级说明
Cache-ControlHTTP/1.1高max-age=3600表示缓存3600秒
ExpiresHTTP/1.0低绝对时间,如Wed, 21 Oct 2026 07:28:00 GMT

两个都存在时,Cache-Control优先。

HTTP/1.1 200 OK
Cache-Control: max-age=31536000
Expires: Thu, 10 Oct 2027 00:00:00 GMT
Content-Type: application/javascript

// JS文件内容...

常见的Cache-Control指令:

max-age=3600        # 缓存3600秒
no-cache            # 不用强缓存,每次都走协商缓存
no-store            # 完全不缓存
public              # 中间代理(CDN)也可以缓存
private             # 只有浏览器可以缓存,CDN不行

协商缓存:缓存过期了,问一嘴服务器

强缓存过期后,浏览器不会直接重新下载,而是先问服务器:"这个资源有没有更新?"

这就是协商缓存,通过两个请求头实现:

方案一:Last-Modified / If-Modified-Since

# 第一次请求,服务器返回:
Last-Modified: Wed, 09 Oct 2026 12:00:00 GMT

# 缓存过期后,浏览器带上:
If-Modified-Since: Wed, 09 Oct 2026 12:00:00 GMT

# 服务器比较时间:
# 没改过 → 304 Not Modified(用本地缓存)
# 改过了 → 200 OK + 新内容

方案二:ETag / If-None-Match(推荐)

# 第一次请求,服务器返回:
ETag: "abc123def456"

# 缓存过期后,浏览器带上:
If-None-Match: "abc123def456"

# 服务器比较ETag值:
# 一样 → 304 Not Modified
# 不一样 → 200 OK + 新内容 + 新ETag

ETag比Last-Modified更精确——文件内容没变但修改时间变了(比如touch了一下),Last-Modified会误判为"已更新",ETag不会。

完整的缓存决策流程

浏览器请求资源
    ↓
检查强缓存(Cache-Control/Expires)
    ↓
  未过期 → 直接使用本地缓存(200 from cache)
    ↓
  已过期
    ↓
发起协商缓存请求(带ETag或Last-Modified)
    ↓
  服务器返回304 → 使用本地缓存
  服务器返回200 → 下载新资源

实际项目中的缓存策略

不同类型文件的缓存策略不一样:

资源类型缓存策略理由
HTMLno-cache入口文件,必须每次都验证
JS/CSS(带hash)max-age=31536000, immutable文件名含hash,内容变了hash就变
JS/CSS(不带hash)no-cache需要协商缓存确认版本
图片/字体max-age=86400缓存一天,平衡更新频率和性能
API响应no-store动态数据,不缓存

Webpack打包时给文件名加content hash是最佳实践:

// webpack.config.js
output: {
  filename: '[name].[contenthash:8].js',
  chunkFilename: '[name].[contenthash:8].chunk.js'
}

这样HTML里引用的是app.a1b2c3d4.js,内容变了hash就变,浏览器会请求新文件。旧文件可以永久缓存,不用担心用户拿到旧版本。

开发者工具看缓存

Chrome DevTools的Network面板能看到每个资源的缓存状态:

  • (disk cache) — 从磁盘缓存读取
  • (memory cache) — 从内存缓存读取
  • (from service worker) — 从SW读取
  • 304 — 协商缓存命中,服务器说"没变"

调试时可以勾选"Disable cache"来跳过缓存,方便验证。


HTTP缓存是前端性能优化成本最低、收益最高的手段之一。配好了缓存策略,二次加载时间能砍掉一大半。

建议把自己项目的Network面板打开看看,有多少请求是304,有多少是真正下载的。