浏览器缓存
浏览器缓存是前端面试里的高频题,本质上考察的是你对 HTTP 缓存策略、资源加载流程、性能优化手段 的理解。
它的核心目标很简单:减少重复请求、降低带宽消耗、提升页面加载速度,并减轻服务端压力。
1. 为什么需要浏览器缓存
如果浏览器每次打开页面都重新请求所有资源,会带来几个明显问题:
- 首屏速度慢,用户感知卡顿
- 重复下载相同的 JS、CSS、图片,浪费带宽
- 服务端承受不必要的高并发请求
- 弱网环境下页面更容易加载失败
所以,浏览器会尽可能复用本地已经拿到的资源,只在必要时再向服务器确认资源是否有变化。
2. 浏览器缓存的两大类
面试里通常会把浏览器缓存分成两类:
- 强缓存
- 协商缓存
它们的区别可以直接记成一句话:
- 强缓存:浏览器认为资源还没过期,直接用本地缓存,不发请求
- 协商缓存:浏览器不确定资源是不是最新,会发请求和服务器确认
3. 强缓存
强缓存命中时,请求甚至不会到达业务服务器,在 Chrome DevTools 里通常能看到 from memory cache 或 from disk cache。
3.1 Expires
Expires 是 HTTP/1.0 的缓存字段,表示一个绝对过期时间。
它的问题是依赖客户端本地时间,如果用户机器时间不准,就可能出现缓存判断偏差。所以现在更常用的是 Cache-Control。
3.2 Cache-Control
Cache-Control 是 HTTP/1.1 中更重要、也更常见的缓存控制字段。
常见取值:
max-age=秒数:资源在多少秒内有效public:响应可以被浏览器和代理服务器缓存private:只能被客户端浏览器缓存no-cache:可以缓存,但每次使用前都要向服务器校验no-store:完全不缓存
其中最容易混淆的是:
no-cache不是“不缓存”,而是“缓存前必须重新验证”no-store才是真的“不落任何缓存”
3.3 强缓存的典型场景
适合做强缓存的资源通常是:
- 带 hash 的 JS 文件
- 带 hash 的 CSS 文件
- 不经常变化的图片、字体等静态资源
比如:
这种策略通常配合构建工具生成的文件指纹使用,例如:
文件内容一旦变化,文件名就会变化,浏览器自然会重新请求新资源。
4. 协商缓存
当强缓存失效后,浏览器会进入协商缓存流程,请求服务器确认资源是否变更。
如果资源没变,服务器返回:
这时浏览器会继续使用本地缓存内容,只是这次发生了一次网络请求。
协商缓存主要有两套方案。
4.1 Last-Modified / If-Modified-Since
服务器第一次返回资源时:
下次浏览器请求同一资源时,会自动带上:
服务器会比较资源最后修改时间:
- 没变,返回
304 - 变了,返回新资源和新的
Last-Modified
它的优点是简单,缺点也明显:
- 只能精确到秒
- 有些文件内容变了,但修改时间不一定可靠
- 有些场景只是重新生成文件,内容没变,时间却变了
4.2 ETag / If-None-Match
相比时间戳方案,ETag 更准确。服务器会为资源生成一个唯一标识。
首次响应:
下次请求:
服务器比较标识:
- 一致,返回
304 - 不一致,返回新资源和新的
ETag
实际项目里,ETag 往往比 Last-Modified 更可靠,因此很多场景会优先使用它。
5. 缓存优先级和工作流程
一个常见的面试回答可以这样描述:
- 浏览器先检查是否命中强缓存
- 如果强缓存命中,直接使用本地资源,不发请求
- 如果强缓存未命中,再发起请求,走协商缓存
- 服务端判断资源是否变化
- 如果未变化,返回
304,浏览器使用本地缓存 - 如果已变化,返回
200和最新资源
可以简化理解为:
6. 浏览器缓存存放位置
前端面试里有时还会继续追问“缓存放在哪”。
常见位置包括:
- Memory Cache:内存缓存,读取速度快,关闭页面后通常会释放
- Disk Cache:磁盘缓存,容量更大,适合持久化资源
- Service Worker Cache:由 Service Worker 接管和缓存,适合离线能力和精细控制
在 DevTools 里常见到:
from memory cachefrom disk cache
这两个只是缓存命中的结果展示,不代表缓存策略只有两种。真正决定是否命中,还是响应头里的缓存规则。
7. 开发中常见的缓存策略
7.1 HTML 文件
HTML 一般不做长期强缓存,因为它负责引用最新的 JS/CSS 文件。
常见策略:
这样浏览器每次都会向服务器确认 HTML 是否更新,但如果没变,依然可以返回 304。
7.2 JS / CSS / 图片等静态资源
静态资源通常做长期强缓存,并配合文件 hash:
7.3 接口数据
接口是否缓存要看业务场景:
- 用户信息、订单状态这类实时数据,通常不做浏览器强缓存
- 字典数据、地区列表、配置项等稳定数据,可以结合业务做短期缓存
需要注意:HTTP 缓存主要针对静态资源最常见,接口缓存通常还会叠加前端内存缓存、客户端存储、CDN 或网关策略。
8. 面试高频追问
8.1 no-cache 和 no-store 有什么区别?
no-cache:可以缓存,但使用前必须校验no-store:不缓存,请求和响应内容都不应被保存
8.2 为什么有了 ETag 还需要 Cache-Control?
因为两者解决的问题不同:
Cache-Control决定要不要直接使用本地缓存ETag决定发请求后,服务器如何判断资源是否变化
前者偏“是否发请求”,后者偏“发请求后如何校验”。
8.3 为什么按了刷新,资源还是可能走缓存?
普通刷新并不等于禁用缓存。浏览器通常会优先遵循缓存策略。
只有强制刷新(例如 Ctrl + F5)才更接近跳过已有缓存重新请求资源,但具体行为也会受浏览器实现影响。
8.4 304 是不是比强缓存更好?
不是。
强缓存优先级更高,性能也更好,因为它连请求都不发。304 虽然不会重复下载资源,但仍然有一次网络往返。
9. Nginx 缓存配置相关知识点
前端面试里如果继续往下问,面试官经常会把“浏览器缓存”和“Nginx 怎么配”连起来问。
这里要先分清两个概念:
- 浏览器缓存:依赖响应头,比如
Cache-Control、Expires、ETag - Nginx 代理缓存:依赖
proxy_cache_*指令,是 Nginx 自己缓存上游响应,不等于浏览器本地缓存
9.1 expires 指令是做什么的
Nginx 的 ngx_http_headers_module 可以给响应添加或修改 Expires 和 Cache-Control。
这也是静态资源缓存最常见的配置入口。
有几个关键点可以直接记住:
expires为正数或0时,会生成Cache-Control: max-age=...expires为负数时,会生成Cache-Control: no-cacheexpires off表示不处理这两个响应头
例如:
这段配置适合带 hash 的静态资源,思路是:
- 文件名变了再重新请求
- 文件名不变就长期走强缓存
9.2 HTML 为什么一般不能配长期强缓存
HTML 通常是入口文件,它里面会引用最新构建产物。
如果你把 HTML 也配成一年强缓存,用户很可能一直拿到旧入口,导致新 JS 文件永远更新不到。
常见写法:
这样做的效果是:
- 浏览器不会直接长期使用旧 HTML
- 每次访问时会向服务端校验 HTML 是否更新
- 如果 HTML 没变,依然可能返回
304
9.3 add_header 的常见用法
add_header 用来追加响应头,经常和 Cache-Control 一起使用。
例如:
这里有两个容易被追问的点:
always表示不管响应状态码是什么,都尽量把这个响应头带上add_header有继承规则,如果当前层级重新写了add_header,上层同名配置不一定会自动叠加,所以排查配置时要看清http、server、location三层关系
9.4 一套常见的前端项目缓存配置
对于前后端分离项目,常见思路是:
index.html走协商缓存- 带 hash 的 JS、CSS、图片走长期强缓存
- 接口响应按业务决定是否缓存
示例:
这套配置很适合 Vue、React、Rspress 这一类静态产物部署。
9.5 proxy_cache 和浏览器缓存不是一回事
很多人提到 “Nginx 缓存” 时,会把 proxy_cache 也混进来。这个要单独分清:
expires、add_header Cache-Control面向的是浏览器proxy_cache面向的是 Nginx 到上游服务之间的响应缓存
也就是说:
- 前者解决“浏览器要不要重新下载”
- 后者解决“Nginx 要不要重新请求后端”
一个基础示例:
这类配置更偏服务端性能优化,适用于:
- 热点但变化不频繁的接口
- CMS 内容页
- 部分列表查询接口
但如果接口带用户身份、权限、购物车、实时状态等信息,就要非常谨慎,否则容易出现缓存串数据的问题。

