为了提升页面加载性能,改善用户体验,我们希望我们的静态资源能够长期缓存在用户的浏览器中,为此,我们通常会给静态资源设置一个较长的 max-age
,而在资源内容有更新时,则生成一个名字不同的新文件,以便用户能够及时收到最新的内容。
我们都知道, HTTP 缓存可以分为强缓存和协商缓存,根据我们的配置,强缓存过期后,浏览器会向服务器发出一个校验请求,如果服务器判断资源没有更新过,则通知浏览器使用缓存,也就是我们常说的 304 响应。
Immutable
但是在现代前端工程体系下,一个静态资源从发布之日起,可能永远也没有更新的需求了(更新后文件名/版本号/hash值等参数一定会变,也就是相当于一个全新的资源了)。但是即使我们配置了较长的 max-age ,用户在刷新页面的时候,浏览器依然会向服务器发出请求,在用户体量较大的时候,对网络资源也有着极大的浪费。按照 Facebook 的统计,大约有 20% 的请求是响应了 304 的。为此, Facebook 建议给 Cache-Control
增加一个新的属性: dont-revalidate
,在响应头里加入该字段表示该资源永不过期,不需要再发送条件请求。这个字段最终被 Firefox 49 根据 Cache Control Extensions 规范实现为一个 Behavioral extension
: immutable
。我们可以这样使用:
1 | Cache-Control: max-age=31536000, immutable |
对于支持 immutable 的浏览器,会一直使用本地的缓存,而对于尚未支持的浏览器,也可以根据 max-age 平滑的降级。
浏览器支持情况
- Firefox: 49+ (仅支持HTTPS请求)
- Microsoft Edge: 15+
- Safari: 24+
- Chrome: 不支持,Chrome 自己实现了另一个解决这个问题的方案
Chrome 中的刷新
在看 Chrome 的新方案之前,我们先来看一下浏览器在刷新时做了什么:
F5

Ctrl + F5

可以看到,在按下 F5 时,Chrome 为请求加上了 max-age=0
的 Cache-Control,从而保证至少要向服务器进行一次过期校验。而在 Ctrl + F5 时,则更为激进的直接设置了 no-cache
。
这里有一张较详尽的表格可以参考:

好像和 immutable 关系不大?我们再来看看 Chrome 中静态资源的刷新:
静态资源刷新
为了方便测试,我们给资源设置了一个 8 秒的 max-age,等待 3 秒后,我们按下 F5:

好像有哪里不对?说好的 max-age=0 呢?说好的 304 呢?
一番操作之后我们找到了这里:
https://bugs.chromium.org/p/chromium/issues/detail?id=654378
https://bugs.chromium.org/p/chromium/issues/detail?id=611416
可以看到,在 Chrome 54+ 版本中,Chrome 开始在刷新页面时只为 main resource
也就是我们的 html 页面加上 max-age=0
的设置,其他资源会遵循资源自身设定的缓存策略。通过修改 reload 的行为, Chrome 同样大量降低了刷新页面带来的带宽消耗。
还是上面的页面,我们等待 10 秒之后再刷新一次验证一下:

可以看到, max-age=8 的静态资源响应变成了 304 ,符合我们的预期。
总结
由于我们的静态资源更新依赖于 html 中资源地址的更新,通常我们会对 html 文件设置为不缓存或仅缓存极短的时间。而绝大部分静态资源本身自发布起就不再需要更新了,所以我们可以大胆的将静态资源的缓存时间设置的足够长(比如一年),同时加入 immutable 属性,这样,几乎所有的现代浏览器都会帮我们取消缓存协商的过程,从而节约大量协商请求的带宽和流量。