浏览器的缓存策略是依靠 HTTP Header 来实现的,共分为两种:
强缓存是指在缓存期间,请求不会发送到服务器,浏览器直接返回缓存结果,需要设置 Header:
expires: Wed, 10 Oct 2020 09:51:00 GMT
expires 是 HTTP/1.0 中用于控制网页缓存的字段,其值代表服务器返回该请求结果的缓存到期时间,也就是说,再次发起同样的请求时,如果客户端时间小于 Expires 的值,浏览器直接返回缓存结果。
由于 expires 是采用客户端时间去和缓存失效时间做对比,但客户端时间是可以做修改的,如果客户端时间和服务端时间并不同步,就会导致强缓存失效,或者时效变少。
所以,在 HTTP/1.1 中增加了 cache-control 头。
cache-control 常见值为:
我们来看个例子:
这个例子中,expires 和 cache-control 都被设置了,但是 cache-control 优先级高,所以该资源会在 2592000 秒(也就是 30 天)后失效。
我们可以得出 2 个结论:
当我们 F12 查看浏览器网络请求的时候,肯定看到过这样的信息,from memory cache(内存缓存)和 from disk cache(磁盘缓存)。
当请求命中强缓存时,浏览器就会从内存或者磁盘中将缓存的资源返回来,请求不会到达服务器。
那么,哪些资源缓存在 memory,哪些缓存在 disk 呢?
关于 memory cache 和 disk cache,Chrome 官方有这么一段描述:
Caching
Chrome employs two caches — an on-disk cache and a very fast in-memory cache. The lifetime of an in-memory cache is attached to the lifetime of a render process, which roughly corresponds to a tab. Requests that are answered from the in-memory cache are invisible to the web request API. If a request handler changes its behavior (for example, the behavior according to which requests are blocked), a simple page refresh might not respect this changed behavior. To make sure the behavior change goes through, callhandlerBehaviorChanged()
to flush the in-memory cache. But don't do it often; flushing the cache is a very expensive operation. You don't need to callhandlerBehaviorChanged()
after registering or unregistering an event listener.
以上引用自 Chrome API
读取 memory 中的缓存资源,肯定要比读取 disk 中的更快,但是 memory 中的缓存,会随着进程的释放而释放,也就是说,一旦我们关闭 Tab 标签,memory 中的缓存也就没有了。
那么哪些资源会被缓存到 memory,哪些会缓存到 disk 中呢?关于这点我也没有找到定论,大多数的观点如下,供大家参考:
如果请求没有命中强缓存,或者强缓存失效后,就需要向服务器发起请求,验证资源是否有更新,这个过程叫做协商缓存。
当浏览器发起请求验证资源时,如果资源没有改变,那么服务器返回 304 状态码,并且更新浏览器缓存有效期;如果资源发生改变,那么服务器返回 200 状态码,并且返回相应资源,更新浏览器缓存有效期。
那么服务器如何确定资源有没有更新呢,这里就要用到以下 2 组 HTTP 头。
last-modified 表示文件的最后修改日期,由服务器添加到 Response Header 中;if-modified-since 由浏览器添加到 Request Header 中,是上一次该资源的 last-modified 值。
服务器收到请求后,会将 if-modified-since 和服务器上该文件的修改时间戳进行比对,如果超过了缓存时间,那么则返回最新的资源,200 状态码,如果还在缓存有效期内,则返回 304 状态码。
上面这个例子可以看到:
Fri, 20 Dec 2019 12:44:01 GMT
Fri, 20 Dec 2019 12:44:01 GMT
这里服务器将 if-modified-since 的时间和服务器上文件的修改时间做比对,发现仍在缓存时间有效期内,所以直接返回 304 状态码,并不返回文件资源,由浏览器提供缓存好的资源。
但是 last-modified 也有它的缺点:
因为以上这些问题,于是在 HTTP/1.1 出现了 etag / if-none-match。
etag 类似于文件指纹,可以对文件内容做摘要算法,比如 md5,生成的值作为 etag 的值,由服务器添加到 Response Header 中,浏览器再次请求该资源时,会在 Request Header 中添加 if-none-match 头,值为上次 etag 的值,服务器收到请求后,会对请求资源再次做相同的摘要算法,和 if-none-match 值进行比对,如果不一样,说明资源更新了,返回 200 以及更新后的资源文件,如果相同,说明文件没有被修改,则返回 304,由浏览器返回缓存资源。
总结来说,last-modified / if-modified-sice 和 etag / if-none-match,就是将服务器返回的某一个值,由浏览器在发送请求的时候带回去,服务器拿到值后和本地文件的某个属性进行判断,来决定是否返回新的资源,还是由浏览器返回缓存资源,这个过程,就叫做协商缓存。
类似于 expires 和 cache-control,etag / if-none-match 的优先级要比 last-modified / if-modified-since 高。
如果什么缓存策略都没有设置,那么浏览器会采用一个启发式的算法,通常会读取 Response Header 中的 date 头,减去 last-modified 值的 10% 作为缓存时间。
学习了上面的缓存策略,在实际场景中我们该如何应用呢?