你才刚刚修复了一个处于紧急状况的 Bug,完成了新版本的部署,之后自己采用隐身模式进行测试,发现一切都是正常的。然而产品经理在其电脑上进行了如下操作,按了五次刷新,可是页面呈现的依旧是老样子。随后打开 DevTools 的 Network 面板,能够看到所有请求所对应的显示都是同一词汇:(disk cache)。就在那一刻,你这才明白过来,浏览器居然把旧代码隐匿在了硬盘当中,仍旧坚持给你展示“过去”的模样。
初始设计浏览器缓存,目的在于削减网络请求,加快页面加载速度,节省流量。它宛如一位尽责的图书管理员,将用户阅览过的书籍存放于身旁,下一次直接递予,无需再往仓库寻觅。此等机制于90%的情景里属于堪称完美的优化举措,唯独在您需要即刻呈现新内容之际,它才会转变为阻碍。
存在着两种工作模式的被称作缓存的事物,它们分别是强缓存以及协商缓存。对于解决问题而言,理解这二者之间存在的区别是十分关键的要点之所在。处于强缓存这一阶段的时候,浏览器完全不会咨询服务器相关信息,而是直接去使用本地所放置的副本。在协商缓存这个阶段,浏览器会首先向服务器发出询问,询问本地缓存是否依旧可以被使用,随后由服务器来做出决定,到底是返回新的内容,还是让浏览器继续去使用原本的旧内容。
于强缓存生效之际,浏览器于缓存有效期之内全然不会向服务器发出请求,其判断依据乃是响应头之中的Cache-Control以及Expires字段;当Cache-Control设定了max-age,又或者Expires设定了未来时间之时,浏览器便认定缓存有效;此时在Network面板里可目睹状态码为200,然Size一栏显示为memory cache或者disk cache,这意味着压根就没经由网络。
磁盘缓存是缓存被写入硬盘之后所呈现的状态,哪怕关闭浏览器之后再次打开,它依旧是存在的。这恰恰就是你在产品经理电脑上面看到的那个主要因素。她连续按了五次刷新按钮,然而浏览器依旧执拗地从硬盘当中读取旧页面,原因是强缓存尚未过期。隐身模式能够正常运行的缘由,是它默认情况下不使用已有的缓存。
带有缓存标识的浏览器去向服务器询问,“我这个资源还能不能继续使用呀?”这便是协商缓存的工作方式,要是服务器给出能用的答复,便返回304状态码,浏览器会接着使用缓存,倘若不是,就返回200还有新内容,此过程最少要发起一次请求,不过304响应基本不会传输数据体,相较于去完全下载新内容,速度要更快些。
被用于协商缓存达成的,主要是借由两对头部来达成:其中一对是Last-Modified和If-Modified-Since,它是基于文件修改时间来构建的;另一对是ETag和If-None-Match,它是依据文件内容生成的唯一标识来形成的。ETag相比Last-Modified更为精确,这是由于,在文件时间出现变化但内容却未改变,或者文件时间没有变化但内容被覆盖重写的状况下,ETag都能够准确无误地进行识别。
若你的index.html被设定了Cache-Control: max-age=3600,那么在这一小时内用户进行访问,浏览器都不会前往服务器获取新的HTML。而HTML里所引用的那些JS、 CSS文件倘若使用了带有哈希的文件名,像main.a1b2c3.js这样,那么即便JS文件有了更新,用户获取到的依旧是旧的HTML,旧HTML里引用的仍是旧的JS路径。
解决方案是挺简易的,HTML文件得设置Cache - Control: no - cache,每次都要朝着服务器去验证,不过要允许服务器返回304以此节省带宽。又或者更严苛地运用Cache - Control: no - store完全不进行缓存,然而如此做会造成性能的损失。JS、CSS等静态资源采用文件名哈希,在内容产生变化时文件名会改变,这样便能安心地设置为期一年的强缓存。
就算你的源服务器配置无误,CDN节点或许依旧缓存着旧有的资源。诸多CDN需要手动去刷新抑或设定合理的缓存策略。在部署新版之际要是忘了调用CDN刷新API,用户从CDN获取到的仍旧是旧的文件。还有一个隐蔽的问题是服务器时间不准确,这可能致使缓存提前失效或者过期之后迟迟不更新。
解决方案是于部署流程里自动去调用CDN刷新API,又或者给资源文件名添加上哈希值,如此一来新资源的URL全然不同,CDN自然而然就会回源拉取最新的内容。对于API数据,依据业务需求设置Cache-Control: no-cache或者短时间的max-age=60,既能够保证数据及时更新,也不至于每次都从数据库进行查询。
当用户按下Cmd+R或者F5的时候,浏览器便是会带上Cache-Control: max-age=0,以此告知服务器“我并不想要强缓存”,然而协商缓存依旧是有可能生效起来的。要是服务器返回304,那么用户所看到的依旧为旧资源。而真正进行硬实时刷新的则是Cmd+Shift+R或者Ctrl+F5,其会对所有缓存予以忽略,强行请求新资源。
于实际工作当中,你会碰到修改了CSS然而样式却未改变的情形。将Network打开,发觉CSS文件乃是disk cache,缘由在于文件名未曾改变且缓存时间设定得过久。在部署新版之后,老用户页面出现错乱状况,通常是其浏览器始终缓存的旧HTML里引用了已被删除的旧JS文件。这些教训向我们表明,缓存策略必须要从源头着手设计妥当。
有一种缓存方案,它是经过无数大厂验证的,是这样的:对于HTML文件在此设置Cache-Control为no-cache,接着每次都要向着服务器去进行验证。而CSS、JS以及图片等这些静态资源,会设置Cache-Control为max-age=31536000且immutable,并且还要配合文件名哈希,一旦内容发生变化文件名就会跟着改变。至于API数据,则要依据业务来做决定,通常会设置为no-cache或者是短时间的max-age。
这一套方案,既确保了更新能够及时实现生效的状态,又在最大限度上对缓存性能进行了充分利用。在你下一回更新代码之后,要是发觉用户仍旧在使用旧版本,先不要急着讲“他们不进行刷新”——极有可能是你的缓存策略在暗地里发挥作用。浏览器缓存并非是恶魔,它属于我们用于优化性能的有力工具,关键之处在于对它加以理解,给予它尊重,善于运用它。
给你在项目当中碰到过的最为奇特怪异的缓存方面的问题究竟是什么,是图片无论如何毫无办法实现更新,又或者是API数据被CDN缓存长达三天之久,欢迎来到评论区域分享你遭遇的那些踩到坑的经历,点赞并且进行转发从而让更多的前端领域的朋友能够避开这些坑。