介绍
提到页面优化,浏览器缓存必定是一个绕不过的话题,判断一个网站的性能最直观的就是看网页打开的速度,而提高网页反应速度的一个方式就是使用缓存。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。因此理解浏览器的缓存机制,就显得尤为重要。
这里我们简单介绍下浏览器的缓存
浏览器中的HTTP
请求是一种应答模式,HTTP
发起请求,服务器响应该请求,那么浏览器如何确定一个资源是否缓存,当请求一个资源的时候,应该去浏览器的缓存取,还是发送请求来获取该资源呢
缓存的关键:
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
我们根据是否向浏览器发起请求分为强制缓存和协商缓存
强制缓存
不会向服务器发送请求,直接从缓存中读取资源,在chrome
控制台的network
选项中可以看到该请求返回200的状态码,并且size
显示from disk cache
或from memory cache
状态码为灰色的请求则代表使用了强制缓存,请求对应的Size
值则代表该缓存存放的位置,分别为from memory cache
和 from disk cache
from memory cache
代表使用内存中的缓存,from disk cache
则代表使用的是硬盘中的缓存,浏览器读取缓存的顺序为memory – disk
。在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache)
;而css
文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)
。
HTTP1.0的缓存
在 HTTP1.0
时代,给客户端设定缓存方式可通过两个字段——Pragma
和Expires
来规范。虽然这两个字段早可抛弃,但为了做http协议的向下兼容,你还是可以看到很多网站依旧会带上这两个字段。
pragma
当该字段值为no-cache
的时候(事实上现在RFC中也仅标明该可选值),会知会客户端不要对该资源读缓存,即每次都得向服务器发一次请求才行。
expires
有了Pragma
来禁用缓存,自然也需要有个东西来启用缓存和定义缓存时间,对HTTP1.0
而言,Expires
就是做这件事的首部字段。 Expires
的值对应一个GMT(格林尼治时间)
,比如Mon, 22 Jul 2002 11:12:01 GMT
来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求。
expires设置的时间是服务器的时间,设置的是资源"失效"的时刻,如果时间客户端的时间和服务器的时间不一致,那这个缓存就没有意义了
HTTP1.1新增的cache-control
针对上述的“ Expires
时间是相对服务器而言,无法保证和客户端时间统一”的问题,http1.1新增了 Cache-Control
来定义缓存过期时间。注意:若报文中同时出现了Expires
和 Cache-Control
,则以 Cache-Control
为准 ,使用格式:
"cache-control":cache-directive
作为响应首部时, cache-directive
的可选值有 :
public
:所有内容都将被缓存(客户端和代理服务器都可缓存)。具体来说响应可被任何中间节点缓存,如 Browser -- proxy1 -- proxy2 -- Server,中间的proxy可以缓存资源,比如下次再请求同一资源proxy1直接把自己缓存的东西给 Browser 而不再向proxy2要。private
:所有内容只有客户端可以缓存,Cache-Control的默认取值。具体来说,表示中间节点不允许缓存,对于Browser -- proxy1 -- proxy2 -- Server,proxy 会老老实实把Server 返回的数据发送给proxy1,自己不缓存任何数据。当下次Browser再次请求时proxy会做好请求转发而不是自作主张给自己缓存的数据。no-cache
:客户端缓存内容,是否使用缓存则需要经过协商缓存来验证决定。表示不使用 Cache-Control的缓存控制方式做前置验证,而是使用 Etag 或者Last-Modified字段来控制缓存。需要注意的是,no-cache这个名字有一点误导。设置了no-cache之后,并不是说浏览器就不再缓存数据,只是浏览器在使用缓存数据时,需要先确认一下数据是否还跟服务器保持一致。no-store
:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存max-age
:max-age=xxx (xxx is numeric)表示缓存内容将在xxx秒后失效s-maxage(单位为s)
:同max-age,只用于共享缓存(比如CDN缓存)。比如当s-maxage=60时,在这60秒中,即使更新了CDN的内容,浏览器也不会进行请求。max-age用于普通缓存,而s-maxage用于代理缓存。s-maxage的优先级高于max-age。如果存在s-maxage,则会覆盖掉max-age和Expires header。
优先级从高到低分别是 Pragma -> Cache-Control -> Expires
Cache-Control使用的是时间间隔,Expires使用的是失效时刻
协商缓存
强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
缓存校验字段
Last-Modified
服务器将资源传递给客户端时,会将资源最后更改的时间以Last-Modified: GMT”
的形式加在实体首部上一起返回给客户端 ,值代表资源上次修改的时间
次再次请求时,会把该信息附带在请求报文中一并带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304
状态码,内容为空,这样就节省了传输数据量 。如果两个时间不一致,则服务器会发回该资源并返回200
状态码,和第一次请求时类似。这样保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。一个304
响应比一个静态资源通常小得多,这样就节省了网络带宽
传递Last-Modified
的请求报文首部字段一共有两个:
- If-Modified-Since: Last-Modified-value
示例为 If-Modified-Since: Thu, 31 Mar 2016 07:07:52 GMT
该请求首部告诉服务器如果客户端传来的最后修改时间与服务器上的一致,则直接回送304 和响应报头即可。
当前各浏览器均是使用的该请求首部来向服务器传递保存的 Last-Modified 值。
- If-Unmodified-Since: Last-Modified-value
该值告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412`(Precondition Failed) 状态码给客户端。 Last-Modified 存在一定问题,如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)
ETag
为了解决Last-Modified
的不准确(文件修改时间改了,但文件内容却没有变 )问题,HTTP1.1
还增加了ETag
实体首部字段, 服务器会通过某种算法,给资源计算得出一个唯一标志符(比如md5标志),在把资源响应给客户端的时候,会在实体首部加上“ETag: 唯一标识符”
一起返回给客户端。
客户端会保留该 ETag
字段,并在下一次请求时将其一并带过去给服务器。服务器只需要比较客户端传来的ETag
跟自己服务器上该资源的ETag
是否一致,就能很好地判断资源相对客户端而言是否被修改过了。 如果服务器发现ETag
匹配不上,那么直接以常规GET 200
回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag
是一致的,则直接返回304
知会客户端直接使用本地缓存即可
传递ETag
的请求报文也有两个:
- If-None-Match: ETag-value
示例为 If-None-Match: "5d8c72a5edda8d6a:3239" 告诉服务端如果 ETag 没匹配上需要重发资源数据,否则直接回送304 和响应报头即可。 当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值
- If-None-Match:ETag-value
告诉服务器如果没有匹配到ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段
总结
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。
用户刷新/访问行为
- 在URI输入栏中输入然后回车/通过书签访问,这讲触发缓存机制
- F5/点击工具栏中的刷新按钮/右键菜单重新加载,浏览器会设置max-age=0,跳过强缓存判断,会进行协商缓存判断
- Ctl+F5,跳过强缓存和协商缓存,直接从服务器拉取资源