前端缓存
缓存大致分为两大块,一块是强缓存,另一块是协商缓存,是由 HTTP 请求的响应头部的几个字段来确定相关的缓存策略;
缓存机制
由于缓存是由响应头决定的,所以在第一次请求时,浏览器会保存相关字段供后续使用;后续请求该资源的流程图如下:
由流程图可知,缓存分为上述的两类,一类可以不请求服务器直接从本地缓存中获得(称为强缓存),另一类需要请求服务器(称为协商缓存),由服务器判断是否可以直接读取缓存;
强缓存
浏览器在第一次请求资源后,再次请求时:
- 优先获取该资源缓存的 header 信息,判断是否命中强缓存,如果命中,则直接从缓存读取,响应的状态码为
200(from cache)
,而与强缓存相关的 header 字段有两个:expires | cache-control
强缓存中,cache-control 的优先级高于 expires。
expires
这个字段是 http1.0 的规范,是一个绝对时间的 GMT 格式的字符串,如果发送请求的时间在expires
之前,那就表示本地缓存是有效的;
缺点在于:服务器时间和浏览器时间不一定是一致的,可能导致该字段失效;
cache-control
这个字段是 http1.1 的规范,主要利用该字段的max-age
的值来进行判断,这个值是一个相对值,表示资源的有效期,比如 7 天,一年等;资源第一次请求时间和max-age
,计算出一个资源过期时间,再那过期时间和当前请求时间比较,如果在过期时间前,就命中缓存;除了max-age
,还有其他可选项:
no-cache
:不使用本地缓存,直接进入服务器请求的协商缓存阶段,这个字段会令强缓存失效,直接走协商缓存;no-store
:禁止浏览器、服务器缓存数据,每次请求时都会向服务器发送请求,每次都会重新下载完整的资源,与第一个的区别就是这个字段根本不经过任何缓存;public
:可以被浏览器,CDN,代理服务器等缓存;private
:只能被浏览器缓存,不允许 CDN,代理服务器等对资源进行缓存;immutable
:与public
的区别是,public 在用户刷新时,会重新向服务器发起请求,而 immutable 不会重新发送请求;
协商缓存
协商缓存是由服务器来确定缓存资源是否可用的,在强缓存未命中或者不存在时,客服端和服务器会通过 HTTP 协议进行通信,在请求头和响应头上分别加上对应的字段,来标识资源是否可以使用;
协商缓存主要涉及两组字段,last-modified | if-modified-since
和etag | if-none-match
,前者是包含在响应头中的,后者是在请求头中;这两组字段都是成对出现的,如果第一次请求的响应头中带上了某个字段,那么下次的请求中也会带上相应的字段;如果响应头没有则后续的请求中也不会有;
last-modified | if-modified-since
这一对的值是 GMT 格式的时间字符串,响应头对应的字段是last-modified
,请求头对应的字段是if-modified-since
,前后端交互过程如下:
- 第一次请求资源时,服务器在返回资源的同时,会在
response headers
加上last-modified
字段,表示这个资源在服务器上的最后修改时间; - 后续再请求这个资源的时候,浏览器会在
request headers
加上if-modified-since
,它的值就是上次请求时返回的last-modified
的值; - 服务器再次受到资源请求时,会根据请求头的
if-modified-since
的值,和资源在服务器上的最后修改时间进行对比,来判断资源是否有变化,如果没有变化,就返回状态码304 not modified
,但不会返回资源的内容;如果有变化,就正常返回资源内容。当服务器返回 304 时,响应头中就不会添加last-modified
字段了,因为资源没变化就表示最后修改时间也没变; - 浏览器收到 304 响应后,会从缓存中加载资源;
- 最后,如果协商缓存没有命中,浏览器直接从服务器加载资源时,
last-modified
会被更新,下次请求时的if-modified-since
也会被更新为最新一次的值;
Etag | if-none-match
这一对是由服务器生成的,资源的唯一标识符,只要资源内容发生变化,这个值就会改变;其判断过程与上一对类似,其中Etag
是响应头包含的,if-none-match
是请求头包含的;区别在于即使服务器返回了 304,响应头里还是会把新生成的Etag
带上,即使Etag
的值没有发生变化;
为什么协商缓存要有两对
这两对的判断方式很类似,为什么要有两对值呢;Etag
是 HTTP1.1 新添加的,主要为了解决以下的问题:
- 一些文件也许会周期性的修改,但其内容不改变,仅是最后修改时间改变了,此时我们不希望客户端认为这个文件被修改了而去重新获取一遍相同的内容;
- 有的文件修改很频繁,比如在秒以下的颗粒度进行修改,而
if-modified-since
能检查到的颗粒度是秒级的,这种修改就无法判断了(UNIX 的文件修改记录 MTIME 智能精确到秒); - 某些服务器不能精确得到文件的最后修改时间;
这些情况下用Etag
去判断会更好一些,Etag
是服务器自动生成的(或者开发者生成的),类似文件的 Hash 值,是唯一标识符;
另外,这两对值是可以一起使用的,Etag
的优先级更高,当Etag
一致的情况下才会对比last-modified
,最后才决定是否返回 304。
用户行为对缓存的影响
用户操作 | expires/cache-control | last-modified/etag |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进后退 | 有效 | 有效 |
F5 刷新 | 无效 | 有效 |
Ctrl+F5 强制刷新 | 无效 | 无效 |
消除强缓存
有时候缓存机制会给页面访问带来一些问题,比如明明更新了资源,访问时却无法显示,对此,详情请看这篇回答:大公司里怎样开发和部署前端代码?-张云龙的回答-知乎
参考
https://www.cnblogs.com/wonyun/p/5524617.html