浏览器缓存是浏览器在本地磁盘对用户最近请求过的资源进行存储,当访问者再次访问同一资源时,浏览器就可以直接从本地磁盘加载资源,通过缓存的方式就可以减少与服务器的数据传输,减少服务器的负担,加快页面响应速度等。
良好的缓存策略可以降低资源的重复加载提高网页的整体加载速度,通常浏览器缓存策略分为强缓存和协商缓存。常见的HTTP
缓存只能存储GET
响应,对于其他类型的响应则不会进行缓存。
理论上来讲,当一个资源被缓存存储后,该资源应该可以被永久存储在缓存中,由于缓存只有有限的空间用于存储资源副本,所以缓存会定期地将一些副本删除,这个过程叫做缓存驱逐。另一方面,当服务器上面的资源进行了更新,那么缓存中的对应资源也应该被更新,由于HTTP
是C/S
模式的协议,服务器更新一个资源时,不可能直接通知客户端更新缓存,所以双方必须为该资源约定一个过期时间,在该过期时间之前,该资源的缓存副本就是新鲜的,当过了过期时间后,该资源的缓存副本则变为陈旧的。驱逐算法用于将陈旧的资源缓存副本替换为新鲜的,注意,一个陈旧的资源缓存副本是不会直接被清除或忽略的,当客户端发起一个请求时,缓存检索到已有一个对应的陈旧资源缓存副本,则缓存会先将此请求附加一个If-None-Match
头,然后发给目标服务器,以此来检查该资源副本是否是依然还是算新鲜的,若服务器返回了304 (Not Modified)
,则表示此资源副本是新鲜的,注意该响应不会有带有实体信息,通过这种方式,可以节省一些带宽。若服务器通过If-None-Match
或If-Modified-Since
判断后发现已过期,那么会带有该资源的实体内容返回。对上面的请求过程可以概括如下:
- 浏览器在发起对于资源的请求时,会首先检查本地是否存在缓存,如果存在缓存则通过
expires
和cache-control
检查缓存是否过期,如果命中缓存且缓存未过期,则直接使用本地缓存。 - 本地缓存未命中,则浏览器向服务器发送一个协商请求,通过
last-modified
和etag
验证资源是否命中协商缓存,如果命中则服务器会将这个请求响应为304
,但是不会返回这个资源的数据,依然是从缓存中读取资源,如果未命中则会携带资源返回且响应为200
。
强缓存是通过Expires
与Cache-Control
来控制缓存在本地的有效期。
Expires
是HTTP 1.0
提出的一个表示资源过期时间的Header
,它描述的是一个绝对时间,由服务器返回。Expires
受限于本地时间,如果修改了本地时间,可能会造成缓存失效.对于资源的请求,如果在Expires
之内,则浏览器会直接读取缓存,不再请求服务器。
Expires: Sun, 14 Jun 2020 02:50:57 GMT
Cache-Control
出现于HTTP 1.1
,优先级高于Expires
,表示的是相对时间,请求头和响应头都支持这个属性,通过它提供的不同的值来定义缓存策略。
Cache-Control: max-age=300
Cache-Control: no-store
: 缓存中不得存储任何关于客户端请求和服务端响应的内容,每次由客户端发起的请求都会下载完整的响应内容。Cache-Control: no-cache
: 缓存中会存储服务端响应的内容,只是在与服务端进行新鲜度再验证之前,该缓存不能够提供给浏览器使用。简单来说,就是浏览器会将服务端响应的资源进行缓存,但是在每次请求时,缓存都要向服务端评估缓存响应的有效性,协商缓存是否可用,根据响应是304
还是200
判断是使用本地缓存资源还是使用服务器响应的资源。Cache-Control: public || private
:public
表示该响应可以被任何中间人比如中间代理、CDN
等缓存。默认响应为private
,private
表示该响应是专用的,中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中。Cache-Control: max-age=31536000
: 响应为最大的过期时间,其指令是max-age=<seconds>
,表示资源能够被缓存即保持新鲜的最大时间,max-age
是距离请求发起的时间的秒数。Cache-Control: must-revalidate
: 当使用了must-revalidate
指令,那就意味着缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过期的缓存将不被使用。在正常情况下是没有必要使用这个指令的,因为在强缓存过期的情况下会进行协商缓存,但是HTTP
规范是允许客户端在某些特殊情况下直接使用过期缓存的,比如校验请求发送失败的时候,还比如有配置一些特殊指令stale-while-revalidate
、stale-if-error
等的时候,must-revalidate
指令就是让缓存在过期后的任何情况下都必须重新验证。
当浏览器对某个资源的请求没有命中强缓存,就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的HTTP
状态为304 (Not Modified)
,该请求不携带实体数据,若未命中,则返回200
并携带资源实体数据。协商缓存是利用的是Last-Modified,If-Modified-Since
和ETag、If-None-Match
这两对Header
来管理的。
Last-Modified,If-Modified-Since
是HTTP 1.0
引入的,Last-Modified
表示本地文件最后修改日期,浏览器会在请求头加上If-Modified-Since
即上次响应的Last-Modified
的值,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,但是如果在本地打开缓存文件,就会造成Last-Modified
被修改,所以在HTTP 1.1
出现了ETag
。
Etag
就像一个指纹,资源变化都会导致ETag
变化,跟最后修改时间没有关系,ETag
可以保证每一个资源是唯一的,If-None-Match
的请求头字段会将上次返回的Etag
发送给服务器,询问该资源的Etag
是否有更新,有变动就会发送新的资源回来。ETag
的优先级比Last-Modified
更高,具体使用ETag
主要出于下面几种情况考虑:
- 一些文件也许会周期性的更改,但是他的内容并不改变,比如仅仅改变的修改时间,这个时候我们并不希望客户端认为这个文件被修改了,而重新
GET
。 - 某些文件修改非常频繁,比如在秒以下的时间内进行修改,例如
1s
内修改了N
次,If-Modified-Since
能检查到的粒度是秒级的,这种修改无法判断。 - 某些服务器不能精确的得到文件的最后修改时间。
https://github.com/WindrunnerMax/EveryDay
https://zhuanlan.zhihu.com/p/60357719
https://www.jianshu.com/p/9c95db596df5
https://segmentfault.com/a/1190000008956069
https://github.com/amandakelake/blog/issues/41
https://juejin.im/post/5ccfccaff265da03ab233bf5
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ