更多文章可以关注 【 小敏碎讲前端 】
一 简单了解缓存位置
- 内存缓存:快速但容量小
- 磁盘缓存:持久化但比较慢
- 可编程缓存:可编程缓存存储 + 业务逻辑代码,让你能够根据实际需求"写程序"来决定缓存的每一个细节,而不是被动接受缓存框架的固定行为
二 协商缓存 与 强制缓存
2.1 协商缓存
每次请求都询问服务器自选是否有更新,返回304或新资源(简单来说就是浏览器拿缓存的"标签"去问服务器"还能用吗",服务器回答"能用"(304)或"不能用了,拿新的吧"(200))
2.1.1 如何实现协商缓存
(1) ETag(优先使用)
服务器给资源打的"版本号"或"指纹":
# 首次响应,服务器返回 ETag
HTTP/1.1 200 OK
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Content-Type: text/html
[资源内容]
# 再次请求,浏览器带上 If-None-Match
GET /index.html HTTP/1.1
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
# 资源未变,服务器返回 304
HTTP/1.1 304 Not Modified
ETag 的计算方式:
- 文件内容的哈希值(如 SHA1)
- 文件大小 + 修改时间的组合
- 服务器自定义的版本号
(2) Last-Modified(备用)
服务器告诉浏览器资源的最后修改时间:
# 首次响应
HTTP/1.1 200 OK
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
# 再次请求,浏览器带上 If-Modified-Since
GET /index.html HTTP/1.1
If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT
# 未修改,返回 304
HTTP/1.1 304 Not Modified
2.1.2 其他知识点
(1) 为什么有 Last-Modified 还要 ETag?
- Last-Modified 精度只到秒,毫秒级变化检测不到
- 文件修改时间可被伪造
- 文件内容未变但修改时间变了(如重新上传)
(2) 304 请求消耗大吗?
- 比完整请求小很多(无响应体)
- 但仍需建立连接、查数据库验证
- 适合验证频率不高的场景
2.2 强制缓存
在缓存有效期内,直接使用本地缓存,不发送请求到服务器(是一种不需要询问服务器,浏览器直接根据本地缓存的过期时间来决定是否使用缓存的机制。)
2.2.1 如何实现强制缓存
(1) Cache-Control
# 服务器响应头
Cache-Control: max-age=3600
常用指令:
| 指令 | 含义 |
|---|---|
max-age=3600 | 缓存3600秒(1小时) |
public | 任何地方都可以缓存(CDN、代理) |
private | 只能浏览器缓存,不能给CDN |
no-cache | 不是不缓存,而是每次都要协商 |
no-store | 完全不缓存 |
immutable | 永不变,适合带hash的静态资源 |
# 组合使用
Cache-Control: public, max-age=31536000, immutable
# 禁止缓存
Cache-Control: no-store
# 必须协商(实际是协商缓存)
Cache-Control: no-cache
(2) Expires(HTTP/1.0,已过时)
Expires: Wed, 21 Oct 2025 07:28:00 GMT
问题:依赖客户端时间,用户改系统时间就会出问题。现在基本被 Cache-Control 替代。
如果同时存在,
Cache-Control优先级更高。
2.3 强缓存 vs 协商缓存
| 对比项 | 强缓存 | 协商缓存 |
|---|---|---|
| 是否请求服务器 | 否(过期前) | 是 |
| 网络消耗 | 0 | 1次请求(无Body) |
| 速度 | 极快(毫秒级) | 较快(有RTT延迟) |
| 数据一致性 | 代码过期前可能不是最新 | 保证最新 |
| 控制头 | Cache-Control | ETag/Last-Modified |
三 缓存流程
用户访问网站
↓
浏览器解析URL
↓
检查缓存策略
↓
内存缓存? → 是 → 200 (from memory cache)
↓ 否
磁盘缓存? → 是 → 缓存过期? → 否 → 200 (from disk cache)
↓ 是 ↓
↓ 协商缓存验证
↓ ↓
发起网络请求 服务器返回304或200
↓
DNS解析 → TCP连接 → TLS握手(HTTPS) → HTTP请求
↓
服务器处理请求
↓
返回响应 + 缓存头
↓
浏览器缓存资源
↓
渲染页面
四 前端后端都需要什么
4.1 职能
前端 :控制文件名哈希,只需要负责打包生成带有哈希的文件名,确保文件内容变化文件名变化
后端:配置响应头策略,根据文件特征配置HTTP响应头。
4.2 浏览器存储的位置由谁控制?
存储位置是缓存在内存还是磁盘,是由浏览器内核自动管理的,基于资源类型,大小,使用频率的启发式算法
总结: 前端管生成哈希,后端配置策略,浏览器管存储位置
五 整个流程梳理
完整缓存流程总结:
开发时:
前端打包 → 生成带哈希文件 → 部署到服务器
第一次访问:
无缓存 → 全部请求 → 服务器返回 + 缓存头 → 浏览器缓存
后续访问:
1. HTML(协商缓存):条件请求 → 304或200
2. 带哈希资源(强缓存):直接读缓存,无请求
3. 新版本发布:
a. HTML返回新版本
b. 新资源文件名变化 → 重新下载
c. 旧资源继续用缓存
4. 一年后:强缓存过期 → 协商缓存验证
以下是详细步骤
1. 前端打包
npm run build
生成结果
- index.html
- app.8a2f1c3e.js
- vendor.a4b5c5d6.js
- style.9e8fdw2.css
2. html自动引入
<!-- 构建工具自动更新引用 -->
<!DOCTYPE html>
<html>
<head>
<link href="/styles.9e8f7a6b.css" rel="stylesheet">
</head>
<body>
<script src="/app.8a2f1c3e.js"></script>
</body>
</html>
3. 后端部署
- 将dist目录部署服务器
- nginx根据文件名自动应用缓存策略
# nginx.conf
server {
listen 80;
server_name example.com;
# 1. HTML文件 - 短时间强缓存(5分钟)
location = /index.html {
root /usr/share/nginx/html;
add_header Cache-Control "public, max-age=300";
# 添加ETag用于协商缓存
etag on;
}
# 2. 带哈希的静态资源 - 1年强缓存
location ~* .[a-f0-9]{8,}.(js|css|png|jpg|jpeg|gif|ico|woff2)$ {
root /usr/share/nginx/html;
expires 1y;
add_header Cache-Control "public, immutable, max-age=31536000";
# 即使有强缓存,也保留ETag
etag on;
}
# 3. 普通资源(无哈希)- 协商缓存
location ~* .(js|css|png|jpg)$ {
root /usr/share/nginx/html;
add_header Cache-Control "no-cache";
etag on;
}
# 4. API接口
location /api/ {
proxy_pass http://backend:3000;
add_header Cache-Control "no-cache";
# API通常也用ETag
etag on;
}
}
4. 浏览器第一次访问
4.1 步骤1: DNS解析 -》TCP连接 -》 TLS握手
4.2 步骤2:请求HTML
# 请求
GET / HTTP/1.1
Host: example.com
User-Agent: Chrome/120.0
Accept: text/html
# 响应
HTTP/1.1 200 OK
Cache-Control: public, max-age=300 ## 桥重点,这里就有缓存的一些规则返回
Content-Type: text/html; charset=utf-8
ETag: "abc123"
Content-Length: 1024
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/styles.1e2f3g4h.css">
</head>
<body>
<script src="/main.3a4b5c6d.js"></script>
< img src="/logo.5d6e7f8g.png">
</body>
</html>
4.3 请求css
# 请求
GET /styles.1e2f3g4h.css HTTP/1.1
Host: example.com
# 响应
HTTP/1.1 200 OK
Cache-Control: public, immutable, max-age=31536000
Content-Type: text/css
ETag: "def456"
Content-Length: 2048
Last-Modified: Mon, 20 Mar 2023 10:00:00 GMT
/* CSS内容 */
4.4 请求js
# 请求
GET /main.3a4b5c6d.js HTTP/1.1
Host: example.com
# 响应
HTTP/1.1 200 OK
Cache-Control: public, immutable, max-age=31536000
Content-Type: application/javascript
ETag: "ghi789"
Content-Length: 4096
Last-Modified: Mon, 20 Mar 2023 10:00:00 GMT
// JavaScript代码
4.5 浏览器缓存资源
// 浏览器内部记录
cache = {
"https://example.com/": {
status: "fresh",
expires: Date.now() + 300000, // 5分钟后过期
response: "<html>...",
headers: { "ETag": "abc123" }
},
"https://example.com/styles.1e2f3g4h.css": {
status: "fresh",
expires: Date.now() + 31536000000, // 1年后过期
response: "/* css */",
headers: { "ETag": "def456" }
},
"https://example.com/main.3a4b5c6d.js": {
status: "fresh",
expires: Date.now() + 31536000000,
response: "// js",
headers: { "ETag": "ghi789" }
}
}
4.6 浏览器根据自身策略缓存
- styles.1e2f3g4h.css (15KB) → 内存缓存(小文件)
- main.3a4b5c6d.js (150KB) → 磁盘缓存(大文件)
- logo.5d6e7f8g.png (50KB) → 磁盘缓存
5. 用户刷新
检查缓存 -》 发起条件请求(根据强制缓存和协商缓存发送不同内容,见本章二) -》 服务器会验证 -》 服务器响应
-
HTML 没有更新, 浏览器会使用缓存中的HTML,状态码304
-
HTM 有更新
6. 前端发布V2版本
6.1 前端重新打包(资源文件名更新)
6.2 用户访问
关键点:只有HTML和实际发生变化了,才会重新下载(打包后资源文件更新了,触发重新下载)
未变化的资源文件继续使用强缓存;
六 其他
6.1 如何避免用户访问到旧版本的前端资源?
"采用多层策略:
构建层:用内容哈希命名静态资源(Webpack的contenthash),文件内容变则文件名变。
缓存策略:
- HTML入口:协商缓存或短时间强缓存(5分钟)
- 静态资源:长期强缓存(1年)+ immutable标记
- API数据:按需设置缓存策略
部署策略:先发布静态资源,再更新HTML引用,避免版本不匹配。
兜底方案:Service Worker管理缓存,版本监控提示用户刷新。
这样既获得了缓存的速度优势,又能保证用户及时获取更新。"
核心原则:HTML 必须能及时更新,它是所有资源的入口。只要 HTML 能获取最新版本,它引用的 JS/CSS 文件名就会变化,自然就会请求新资源。