当前位置: 首页 > 科技观察

一文读懂浏览器缓存

时间:2023-03-12 16:39:49 科技观察

浏览器缓存一直是一个老生常谈的话题,也是面试官经常用来识别面试官的利器。作为前端,这些知识是必须要掌握的。性能优化的有效方法。本文将从缓存原因、缓存读写顺序、缓存位置、缓存策略等角度介绍浏览器缓存,最后给出实际应用示例。为什么需要缓存很多同学都知道缓存的位置和字段以及使用方法,但是大家有没有想过为什么我们的页面需要浏览器缓存呢?缓存可以减少用户等待时间,提升用户体验,直接从内存或磁盘中获取数据。缓存数据肯定比从服务器请求快;减少网络带宽消耗:对于网站运营商和用户来说,带宽就代表着成本,过多的带宽消耗需要额外付费。试想一下,如果可以使用缓存,只产生极少的网络流量,将有效降低运营成本。减轻服务器压力:为网络资源设置有效期后,用户可以复用本地缓存,减少对源服务器的请求,减轻服务器压力。缓存读写顺序当浏览器请求一个资源(比如外部链接a.js)时会发生什么?请从缓存的角度大致说明一下:1.调用ServiceWorker的fetch事件获取资源;2.检查内存缓存;3.检查磁盘缓存;这里再细分一下:如果有强制缓存,而且还没有过期,就使用强制缓存,不请求服务端。此时状态码都是200;如果有强制缓存但已经过期,则使用协商缓存,比较后判断是304还是200;4.发送网络请求,等待网络响应;5.将响应内容存入磁盘缓存(如果请求头信息配置可以保存);6.将响应内容的引用存储在内存缓存中(忽略请求头信息的配置,不存储的除外);7、将响应内容存储在ServiceWorker的CacheStorage中(如果ServiceWorker脚本调用cache.put());上面这一系列过程其实就是浏览器查找缓存并在缓存中存储资源的执行过程。里面专业词汇很多,看得人一头雾水。下面将从缓存位置和缓存策略两个角度简单介绍一下浏览器的缓存。缓存位置从浏览器开发者工具Network面板下的Sizeofarequest可以看到当前请求资源的大小和来源。从这些来源中,我们知道资源是从内存缓存中读取的还是从磁盘缓存中读取的,或者是由服务器返回的。这些是缓存位置:ServiceWorker是在指定源和路径下注册的事件驱动的工作线程;它的特点是:它运行在worker的上下文中,所以它不能访问DOM;独立于主线程,不会造成阻塞;设计是完全异步的,所以ServiceWorker中不能使用同步API(比如XHR和localStorage);最后,出于安全考虑,必须在HTTPS环境下使用;这么多功能,跟缓存有什么关系?其实它有一个功能就是离线缓存:ServiceWorkerCache;不同于浏览器内部的内存缓存和磁盘缓存,它允许我们自己控制缓存。具体操作过程参考Using_Service_Workers;ServiceWorker设置的缓存会出现在浏览器开发者工具Application面板下的CacheStorage中。内存缓存是浏览器内存中的缓存。与磁盘缓存相比,它的特点是读取速度快,但容量小,时效性差。一旦关闭浏览器标签页,内存缓存就会被清空。内存缓存会自动缓存所有资源吗?答案肯定是否定的。当HTTPheader设置为Cache-Control:no-store或者浏览器设置为Disabledcache时,资源无法存储到内存中。事实上,它们无法存储。硬盘。从内存缓存中查找缓存时,不仅会匹配资源的URL,还会检查其Content-type是否相同。磁盘缓存,也称为HTTP缓存,是一种存储在硬盘中的缓存。根据HTTP头中的各个字段,决定资源的缓存规则,比如是否可以缓存,什么时候过期,过期后是否需要重新发起请求?与内存缓存相比,磁盘缓存具有存储空间长等优点,网站中的大部分资源都存储在磁盘缓存中。浏览器如何判断一个资源是存放在内存中还是硬盘中?对于这个问题,网上说法不一,但比较靠谱的观点是:对于大文件,大概率会存储在硬盘中;如果当前系统内存占用率高,文件优先存放在硬盘上。缓存是根据缓存位置来划分的。事实上,还有一个HTTP/2内容推送缓存。由于HTTP/2目前在国内还没有广泛使用,加上网上关于pushcache的知识也不全,本文不打算对此进行介绍。有兴趣的可以看这篇文章:HTTP/2推送比我想象的更难缓存策略按照HTTP头的字段可以分为强缓存和协商缓存。强缓存可以直接从缓存中读取资源返回给浏览器,而不需要向服务器发送请求,而协商缓存是指当强缓存失效时(过期时间),浏览器需要携带缓存向服务器发送请求的标识符,服务器根据缓存ID决定是否使用缓存的过程。强缓存的字段有:Expires和Cache-Control。协商缓存的字段是:Last-Modified和ETag。ExpiresExpires是一个HTTP/1.0字段,表示缓存过期时间,是一个GMT格式的时间字符串。Expires需要在服务器端配置(具体配置取决于服务器)。浏览器会将到期日期与客户端时间进行比较。如果过期时间还没有到,就会从缓存中读取资源。如果已经过期,浏览器判断该资源不再新鲜,需要重新从服务器获取。由于Expires是绝对时间,受限于客户端时间的准确性,可能导致浏览器判断缓存无效。下面是一个Expires的例子,是一个日期/时间:Expires:Wed,21Oct202007:28:00GMTCache-Control是HTTP/1.1的一个字段,里面有很多值:max-age最大缓存时间,单位为值为seconds,在此期间浏览器不需要请求浏览器。该设置解决了Expires中客户端系统时间不准确导致缓存失效的问题;must-revalidate:如果超过max-age时间,浏览器必须向服务器发送请求,验证资源是否仍然有效;公共响应可以被任何对象(客户端、代理服务器等)缓存;私有响应只能由客户端缓存;no-cache跳过强缓存,直接进入协商缓存阶段;no-store不缓存任何内容,设置这个后资源也不会缓存到内存和硬盘;Cache-Control的取值可以混合,例如:Cache-Control:private,max-age=0,no-cache混合时,它们的优先级如下图所示:“当Expires和Cache-Control都存在时已设置,浏览器将优先考虑后者。”当强缓存失效时,会进入协商缓存阶段。具体如下:浏览器在本地搜索强缓存,发现无效,然后向服务器请求缓存标识,服务器使用缓存标识和对应的字段来验证资源是否被访问过修改的。如果没有,那么响应状态会是304,请求的资源不会返回,而是直接从浏览器缓存中读取。浏览器缓存标识可以是:Last-Modified和ETag:Last-Modified资源的最后修改时间;第一次请求时,响应头会返回该字段,告知浏览器该资源的最后修改时间;浏览器将值和资源存储在缓存中;当再次请求资源时,如果强缓存过期,浏览器会将请求头的If-Modifined-Since字段值设置为缓存中保存的最后一个响应头Last-Modified的值,并发送请求;服务器将If-Modifined-Since的值与Last-Modified进行比较。如果相等,则表示资源没有被修改,响应为304;如果不是,说明资源已经被修改,响应为200,返回请求的资源。如果资源的更新速度小于1秒,那么这个字段就会无效,因为Last-Modified时间是精确到秒的。所以有ETag。ETag是根据资源内容生成的唯一标识符。判断一个资源是否被修改的过程同上,只是替换了相应的字段。Last-Modified被ETag取代,If-Modifined-Since被If-None-Match取代。Last-Modified和ETag同时设置时,浏览器会优先考虑后者。浏览器行为在浏览器地址栏输入网址,回车:检查磁盘缓存中是否有匹配。可用时使用;如果没有则发送网络请求。普通刷新(?+R):因为TAB页没有关闭,所以内存缓存可用,会优先使用(如果匹配),其次是磁盘缓存。ForceRefresh(?+?+R):浏览器不使用缓存,所以发送的请求头中有Cache-control:no-cache(为了兼容,还包含了Pragma:no-cache)。服务器直接返回200和最新的内容。当在开发者工具的Network面板下设置Disabledcache禁用缓存时,浏览器不会从内存缓存或磁盘缓存中读取缓存,而是直接发起网络请求。缓存应用程序静态资源。比如一个页面引入了一个JQuery。对于页面来说,这个脚本就是一个工具库,基本不会变。对于这种资源,它的缓存时间可以设置的长一点,比如下面地址脚本:你会看到在响应头中设置了,max-age=2592000直接缓存30天:cache-control:public,max-age=2592000频繁变化的资源对于经常变化的资源,比如一个页面经常需要调整,那么这个页面每次请求的时候都需要校验。可以在响应头中这样设置:cache-control:no-cache不缓存。当然,并不是所有的请求都可以缓存。不能被浏览器缓存的请求如下:HTTP头中包含Cache-Control:no-cache,pragma:no-cache(HTTP1.0),orCache-Control:max-age=0等告诉浏览器不缓存请求;需要根据cookies、认证信息等判断输入内容的动态请求无法缓存;通过HTTPS安全加密的请求;POST请求不能被缓存;不包含Last-Modified/Etag和Cache-Control/Expires的HTTP响应头不能被缓存;