浏览器缓存机制
缓存是性能优化的中非常重要的一环,接下来以三部分来把浏览器的缓存机制说清楚:
- 强缓存
- 协商缓存
- 缓存位置
强缓存
浏览器的缓存作用分为两种情况,一种是需要发送HTTP请求,一种是不需要发送请求。
首先是检查强缓存,这个阶段是 不需要 发送HTTP请求。那么如何检查呢?通过相应的字段进行检查。
- 在HTTP/1.0时期,使用 Expires 即过期时间,存在服务端返回的响应头中,告诉浏览器在过期时间前可以直接从缓存里面获取数据,无需要再请求。 但是,这个潜藏一个大坑:服务器时间和浏览器时间可能并不一致,所以这种方式很快就被抛弃。
- 在HTTP1.1中,采用了一个非常关键的字段:Cache-Control ,存在服务器返回的响应头中,采用过期时长来控制缓存,对应的字段是 max-age。比如这个例子:
Cache-Control:max-age=3600
它可以组合其他指令,完成更多场景的缓存判断:
- public :客户端和代理服务器都可以缓存。
- private : 只有浏览器能缓存,中间的代理服务器不能缓存。
- no-cache : 跳过当前的强缓存,发送HTTP请求
- no-store : 不进行任何形式缓存
- s-maxage : 这和max-age 长得像,区别在于s-maxage是针对代理服务器的缓存时间。
注意:当Expires和Cache-Control同时存在的时候,Cache-Control会优先考虑。当然还有一种情况,当资源缓存时间超时了,也就是强缓存失效了,接下来,就进入第二级屏障 ---- 协商缓存
协商缓存
强缓存失效后,浏览器在请求头中携带响应的缓存tag向服务器发送请求,由服务器根据那个tag,来决定是否使用缓存,这就是协商缓存。
缓存tag分为两种: Last-Modified 和 ETag。这两种各有优势,并不存在谁有绝对优势。
Last-Modified
即最后修改的时间,在浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段。
浏览器接收到后,如果再次发出请求,就会在请求头上携带 If-Modified-Since 字段,这个字段的值也就是服务器传来的最后修改时间。
服务器拿到请求头的 If-Modified-Since 字段后,其实就会和这个服务器中的该资源的最后修改时间作对比:
- 如果请求头中的值小于最后修改时间,说明是时候更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。
- 否则返回304,告诉浏览器直接用缓存。
Etag
ETag 是服务器根据当前文件的内容,给文件生成的唯一标识,只要里面的内容有改动,这个值就会变。服务器通过响应头把这个值给浏览器。
浏览器接收到ETag的值,会在下次请求时,将这个值作为If-None-Match这个字段的内容,并放到请求头中,然后发给服务器。
服务器接收到If-None-Match后,会跟服务器上该资源的ETag进行比对:
- 如果两者不一样,说明要更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。
- 否则返回304,告诉浏览器直接用缓存。
两者对比
- 在精准度上,ETag优于Last-Modified。优于 ETag 是按照内容给资源上标识,因此能准确感知资源的变化。而 Last-Modified 就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况:
- 编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效。
- Last-Modified 能够感知的单位时间是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last- Modified 并没有体现出修改了。
- 在性能上,Last-Modified优于ETag,也很简单理解,Last-Modified仅仅只是记录一个时间点,而 Etag需要根据文件的具体内容生成哈希值。
另外,如果两种方式都支持的话,服务器会优先考虑ETag。
缓存位置
前面我们已经提到,当强缓存命中或者协商缓存中服务器返回304的时候,我们直接从缓存中获取资源。那这些资源究竟缓存在什么位置呢?
浏览器中的缓存位置一共有四种,按优先级从高到低排列分别是:
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
总结
首先通过 Cache-Control 验证强缓存是否可用
- 如果强缓存可用,直接使用
- 否则进入协商缓存,即发送 HTTP 请求,服务器通过请求头中的If-Modified-Since或者If-None-Match字段检查资源是否更新。
- 若资源更新,返回资源和200状态码
- 否则,返回304,告诉浏览器直接从缓存获取资源