作为前端的我,经常会遇到这几种场景,比如说,“面试官:304是什么状态码,可以具体说说么?”;“非前端伙伴:这个地方你改了么,怎么没有变化呀;前端伙伴:亲,强制刷新试试”······没错啦,今天说的就是它——浏览器之缓存术。
什么是浏览器缓存?
浏览器缓存(Browser Caching):为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样可以加速页面访问的阅览。
打开浏览器控制台可以看到如下图所示:

浏览器端的缓存机制种类较多,总体可以归纳为以下九种
- HTTP 缓存
- Local Storage
- Session Storage
- IndexedDB
- Web SQL
- Cookies
- Cache Storage
- Application Cache
- flash 缓存
flash缓存这种方式基本不用,原理是基于flash可以读写浏览器端本地目录,同时也可向js提供api,通过js调用flash去读写特定磁盘目录达到缓存数据目的。
本次我只对HTTP缓存做一个分析说明
缓存位置
从缓存位置上可以分为四种,并且各自都有优先级,依次查找缓存且都没有命中时候才会去请求网络
- Service Workers
Service Wokers 是运行在浏览器背后对独立线程,因为Service Workers中涉及到请求拦截,所以传输协议必须使用HTTPS来保障安全。Service Workers到缓存与浏览器其他内建缓存机制不同,可以让我们自由控制缓存那些文件,如何匹配缓存,如何读取缓存,并且是缓存是持续性的 - Memory Cache
Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 - Disk Cache
Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中。 - Push Cache
Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它是在会话中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。
缓存过程分析
浏览器与服务器通信方式为应答模式,如下图所示

用对话形式大白话翻译
第一阶段:
browser:我需要渲染一个a文件,先去cache这儿问问有没有
cache:我没有a文件,你去问server拿吧
browser:通过http方式找到server,给我一份a文件吧
server:好的,我有a文件,给你200
第二阶段:
browser:我还要渲染一次a文件,再去问问cache
cache:上次从server哪儿拿过来的a文件正好我存来一份,我看了下,还没失效(expired),哪去用吧
browser:nice
第三阶段:
browser:疯了,还要渲染一次a文件,再去问问cache吧
cache:妈耶,我这儿的a文件好像过期了,你再去server哪儿取一下吧
browser:通过http方式找到server,再给我一份a文件吧
server:嗯,我看了下a文件没有被修改过(last-modified),cache那的a文件你还可以用,给你304你去cache你重新去cache哪儿取吧
browser:好嘞
通过以上三个阶段,我们可以知道:
- 浏览器每次发起请求,都会先在浏览器缓存中查找请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存表识存入浏览器缓存中
所以我们可以得到结论浏览器的缓存机制就是它确保了请求的缓存存入与读取,我们可以根据是否需要想服务器重新发起HTTP请求将缓存过程分为强缓存和协商缓存
强缓存
不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network中可以看到该请求返回的200状态码,并且Size显示from disk cache 或from memory cache,强缓存可以通过设置两种HTTP Header 实现: Expires 和Cache-Control
- Expires
Http1.0 中的标准,表明过期时间,注意此处的时间都是指的是服务器的具体时间点。
存在的问题:服务器时间与客户端时间的不一致,就会导致缓存跟期待效果出现偏差 - Cache-Control
Http1.1 中的标准,可以看成是 expires 的补充。使用的是相对时间的概念
简单介绍下Cache-Control的属性设置 - max-age:设置缓存的最大的有效时间,单位为秒(s)。max-age会覆盖掉expires
- s-maxage:只用于共享缓存,比如CDN缓存。与max-age的区别:max-age用于普通缓存
- public:响应会被缓存,并且在多用户之间共享,默认是public
- private:响应只作为私有的缓存,不能在用户之间共享。如果要求HTTP认证,响应会自动设置为private
- no-cache:指定不缓存响应,表明资源不进行缓存。但是设置了no-cache之后并不代表浏览器不缓存,而是在缓存前要想服务器确认资源是否被更改。
- no-store:绝对禁止缓存
- must-revalidate:如果页面过期,则去服务器重新获取
Expires和Cache-Control两者对比
两者差别不大,区别在于Expires是http1.0产物,Cache-Control是http1.1产物,两者同时存在时,Cache-Control的优先级高于Expires;在某些不支持http1.1的环境下,Expires就会发挥作用
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存表识向服务器发起请求,由服务器更加缓存表识决定是否使用缓存的过程,主要有两种情况
- 协商缓存生效,返回304和Not Modified
- 协商缓存失效,返回200和请求结果
协商缓存可以通过设置两种HTTP Header实现:Last-Modified 和 ETag
- Last-Modified 和If-Modified-Since
浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和header浏览器下一次请求这个资源,浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200
Last-Modified存在的一些弊端:
如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源 - ETag 和If-None-Match
ETag是服务器响应请求时,返回当前资源文件的一个唯一表识(由服务器Hash生成),只要资源有变化,ETag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
ETag和Last-Modified对比
- 精确度上,ETag优于Last-Modified
- 性能上,ETag逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器算法计算出一个hash值
- 优先级上优先考虑ETag
总结
本篇文章先写到这儿,目的只为了让自己更好的去理解浏览器的缓存机制,文章中好多内容都是借鉴了好多大佬的分析来加深自己的理解,虽然对于浏览器缓存的整体有了一定的理解,但是在具体缓存机制下还需要进一步学习研究。不过通过写博客去强迫自己进步这个方式真的很不错,希望还在犹豫的小伙伴尽快进入状态,一起学习,一起共勉。