这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战。
HTTP版本升级
你可能还听过 Expire 响应头可以用于强缓存,Last-Modified 响应头配合 If-Modified-Since 请求头可以用于协商缓存。为什么会有两套类似的方案呢?看看它们对应的 HTTP 版本就知道了:
- HTTP/1.0 引入了
Expire
、Last-Modified
和If-Modified-Since
。 - HTTP/1.1 引入了
Cache-Control
、Etag
和If-None-Match
。
所以,这其实是一次升级,HTTP/1.1 引入的方案解决了之前的方案的一些问题:
Expire
指定缓存过期时间为某个具体的时刻,但如果用户浏览器的时间是错的,那么缓存时间就不符合预期了。Cache-Control
则改为指定缓存时长来规避这个问题。Last-Modified
用修改时间来表示文件的版本,但如果一秒内修改两次,就无法区分版本了。Etag 则改为用哈希来描述文件的版本来规避这个问题。If-Modified-Since
是浏览器让服务器对比两个文件的修改时间,也存在上面的问题。If-None-Match
则是让服务器对比两个文件的哈希来规避这个问题。
实际工作中,程序员可能会同时使用这两套方案,以兼容一些旧的网络设备和浏览器。
缓存文件
即使没有 Cache-Control
响应头,当响应的状态码为 200、203、206、300、301、410 时,网络设备或浏览器可能会将其缓存起来。
一般来说,静态文件、get请求得到的 JSON 响应,都可以被缓存起来。
如果你不希望一个响应被缓存起来,可以在响应头里写:
Cache-Control: max-age=0
这可以让当前内容立即过期(如果在这之前还有更早地缓存,同样立即过期)。浏览器对于这样的内容:
- 可以将其保存起来,用于内容协商(俗称协商缓存),即虽然不缓存它,但是在发请求向服务器验证得到 304 状态码后,依然可以复用它。
- 也可以在下一次相同 URL 的请求出现时,直接使用过期的缓存作为响应。
如果你不想要浏览器使用过期的缓存,你可以在响应头里这样写:
Cache-Control: max-age=0, must-revalidate
这样一来,该响应会立即过期,但过期的缓存可以用于内容协商。
如果你不希望响应被保存起来用于内容协商,可以在响应头里写
Cache-Control: no-store
。那么本次响应就既不会被缓存,也不会被保存起来用于内容协商。 但这样写并不会使以前存在的缓存失效,所以一般会和 max-age=0 一起使用:Cache-Control: no-store, max-age=0
。
实际中的缓存用法
- index.html 不能缓存,响应头里加
Cache-Control: max-age=0
防止缓存,加 Etag 用于内容协商,加 Expire: 过去的时间 用于兼容旧浏览器。 - CSS、JS 和图片文件一律在文件名后面加版本字符串,如
index-855fcfd82e.js
,响应头里加Cache-Control: max-age=2592000
缓存一个月以上,Etag 可以不加(因为内容不会变,过时的缓存可以用)。CDN 服务商一般都会帮你做这些事。 - 当 CSS、JS和图片文件内容有变化时,直接修改文件名中的版本字符串(Webpack 会帮你做),替换 index.html 里的旧文件名即可。旧文件的缓存会一直躺在用户的电脑里直到过期后被浏览器自动清理。
- API 响应头里加
Cache-Control: no-store, max-age=0
防止缓存和内容协商。 另外,Cache-Control 既可以出现在请求头里,也可以出现在响应头里。以上说的Cache-Control
都是指响应头,而不是请求头,出现在请求头中含义会有所不同,暂时不讨论。
最后说一句
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。