浏览器缓存策略
首先,浏览器缓存主要分为两种:
- 强缓存
- 协商缓存
先判定 强缓存,然后判定 协商缓存。
缓存位置
首先,我们来看一下缓存之后的资源存在哪里。
我们浏览器支持以下的缓存位置:
- Service Worker
- Disk Cache
- Memory Cache
Disk Cache 和 Memory Cache
我们来详细看一下 Disk Cache 和 Memory Cache 的共同点和不同点:
| Memory Cache | Disk Cache | |
|---|---|---|
| 存储位置 | 存储在 RAM 中,访问速度快 | 存储在磁盘中,适合较为长期的存储 |
| 存储时长 | 仅在当前会话有效,进程退出后被清除 | 跨会话有效,资源到期前保留 |
| 读取性能 | 非常快 | 较慢 |
| 优点 | 高速响应 适合需要频繁读取的资源 | 节省内存空间,适合长期缓存和大文件 |
| 缺点 | 易被清除,关闭浏览器进程就会退出 | 访问速度慢,需要 IO 开销 |
| 缓存适用的资源类型 | JS、字体、小图片等 | CSS、大图片、音视频、静态 HTML 等资源 |
为什么 js 存在 memory cache,css 存在disk cache 呢?
答:因为 css 文件加载一次就可以渲染出来,我们不会进行频繁的读取,所以不适合存在 memory cache 中。但是 js 脚本有可能随时被读取,如果在 disk cache 中的话,会导致 IO 开销很大,从而导致浏览器可能失去响应。
这些都是浏览器进行分类(根据使用频率,资源类型,资源大小,生命周期等)管理的,但是我们通常认为 不需要进行频繁读取的资源 倾向于存在 Disk Cache。
缓存命中优先级
- 优先查找 Memory Cache;
- 若没有找到,查找 Disk Cache;
- 若没有找到,进行网络请求。
强缓存
当请求资源的时候,资源是之前请求过并使用强缓存,那么不会向服务器端再次发送资源请求,浏览器直接通过缓存(根据缓存优先级)获取资源(不管资源有没有进行过改动),从而加快了资源加载的速度。
是否命中强缓存主要依据 header 中的两个字段:expires 和 Cache-Control。
Expires
这个字段主要是 http1.0 的规范,它是一个 绝对时间 的时间字符串,比如:Expires:Mon,18 Oct 2066 23:59:59 GMT 。这个时间代表资源失效的时间,在这个时间之前,会命中强缓存。
这个方式的缺点是:因为是绝对时间,如果服务端和客户端时间偏差大的时候,会导致内存混乱。
Cache-Control
这个字段是 http1.1 出现的 header 信息,主要是根据这个字段中的 max-age 值进行判断,它是一个 相对时间,比如:Cache-Control:max-age=3600,代表资源有效时间是 3600 秒。除了这个字段,Cache control 还拥有以下较为常用的字段:
- No-cache: 需要进行协商缓存。
- No-store: 禁止使用缓存。
- Public: 可以被所有用户缓存,包括终端用户和 CDN 等中间代理器。
- Private(默认): 只允许终端用户缓存,不允许 CDN 等中间代理器缓存。
可以在服务端同时启用 Expires 和 Cache-Control。Cache Control的优先级高于 expires。
协商缓存
当请求资源的时候,强缓存过期或者未采用强缓存,客户端向服务端请求的时候带上特殊字段。如果协商缓存生效,服务端返回 304 状态码(但是不带任何的数据),来表示服务端对资源未进行更新。
客户端在请求 header 中可以带上 Last-Modify/If-Modify-Since 和 ETag/If-None-Match。
Last-Modify/If-Modify-Since
Last-Modify 是一个时间标识代表资源最后更改的时间。这个字段会在浏览器第一次请求的时候,服务端的返回放在 header 中带过来。
当浏览器再次请求这个资源的时候,请求的 header 中带上这个字段。服务端收到之后进行比较,决定是否命中协商缓存。
缺点
- 周期性变化:如果资源改了两次又改回原来的样子了,这个字段的返回不会命中协商缓存(尽管资源跟客户端目前所有的是一致的)。
- 时间精度:最低到秒,如果在很短的时间内发生变化,这个字段不会发生改变。
ETag/If-None-Match
这个字段返回的是一个校验码,ETag 可以保证每个资源都是唯一的,资源的改变都会导致 ETag 的变化。其流程跟 last-modify 一致。
可以同时启用 Last-Modify 和 ETag。ETag 的优先级高于 Last-Modify。服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回 304。
总结
当浏览器再次访问一个已经访问过的资源时,它会这样做:
- 看看是否命中强缓存,如果命中,就直接使用缓存了。
- 如果没有命中强缓存,就发请求到服务器检查是否命中协商缓存。
- 如果命中协商缓存,服务器会返回 304 告诉浏览器使用本地缓存。
- 否则,返回最新的资源。
如果资源命中强缓存了,但是如果更新了资源,怎么做?
在文件的 URL 后面添加一个唯一标识(版本号、哈希值或时间戳)。
例如,
style.css?v=20240101我们常见的构建工具 (比如 webpack,vite 等)等就支持自动为文件生成唯一的哈希标识作为版本号。