前端缓存详解

article/2025/3/15 11:43:02

目录

前言

一、按缓存位置分类

HTTP状态码及区别

几种状态的执行顺序

Memory Cache

Disk Cache

Service Worker

请求网络

二、HTTP 缓存

HTTP 缓存分类

强缓存原理

协商缓存原理

更新和废弃缓存

三、缓存小结

四、缓存的优点

五、浏览器缓存策略

五、缓存的应用模式

六、一些案例

参考资料


前言

缓存机制无处不在,有客户端缓存,服务端缓存,代理服务器缓存等。

一、按缓存位置分类

按缓存位置分类,可以分为:Service Worker,Memory Cache,Disk Cache。

在 Chrome 浏览器开发者工具的Network的Size栏会出现的四种情况:

  • from ServiceWorker
  • from memory cache
  • from disk cache
  • 资源本身大小(比如:13.6K)

HTTP 协议头中的缓存字段,如 Cache-Control, ETag, max-age,都属于 disk cache 的范畴。

Chrome 的隐私模式下,几乎都是 from memroy cache,一些图片是 from disk cache。

这4种情况的优先级:

  1. 先查找 ServiceWorker,如果 ServiceWorker 中存在,从 ServiceWorker 中加载;
  2. 如果 ServiceWorker 中未查找到,选择内存获取,如果内存中存在,从内存中加载;
  3. 如果内存中未查找到,选择硬盘获取,如果硬盘中有,从硬盘中加载;
  4. 如果硬盘中未查找到,那就进行网络请求;
  5. 加载到的资源缓存到硬盘和内存;

HTTP状态码及区别

  • 200 form memory cache
    不请求网络资源,资源在内存当中。浏览器关闭后,数据将被清除,再次打开相同的页面时,不会出现from memory cache。

  • 200 from disk cache
    不请求网络资源,在磁盘当中,关闭浏览器后,数据依然存在,此资源不会随着该页面的关闭而释放,下次打开仍然会是from disk cache。

  • 304 Not Modified
    请求服务端发现资源没更新,使用本地缓存资源。

几种状态的执行顺序

现加载一种资源(例如:图片):
浏览器访问 -> 200(网络请求资源) -> 退出浏览器
再进来-> 200(from disk cache) -> 刷新 -> 200(from memory cache)

Memory Cache

memory cache 叫内存缓存,几乎所有的网络请求资源都会被浏览器自动加入到 memory cache 中。而如果极端情况下 (例如一个页面的缓存就占用了超级多的内存),那可能在 TAB 没关闭之前,排在前面的缓存就已经失效了。

预加载(preload)请求过来的资源就会被放入 memory cache 中,供之后使用。

memory cache 机制保证了一个页面中如果有两个相同的请求 (例如两个 src 相同的 <img>,两个 href 相同的 <link>)都实际只会被请求最多一次,避免浪费。

不过在匹配缓存时,除了匹配完全相同的 URL 之外,还会比对他们的类型,CORS 中的域名规则等。因此一个作为脚本 (script) 类型被缓存的资源是不能用在图片 (image) 类型的请求中的,即便他们 src 相等。

如果想让 memory cache 也不存储,那就需要在 Http 请求头设置 no-store。

Disk Cache

disk cache 也叫 HTTP cache,是磁盘缓存。它允许相同的资源在跨会话,甚至跨站点的情况下使用,例如两个站点都使用了同一张图片。

disk cache 会严格根据 HTTP 头部中的各类字段来进行缓存。当命中缓存之后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求还是快了不少的。绝大部分的缓存都来自 disk cache。

凡是持久性存储都会面临容量增长的问题,disk cache 也不例外。浏览器会根据自身算法自动清理“最老的”或者“最可能过时的”资源。

浏览器如何决定将资源放进内存还是硬盘呢?主要策略如下:

  • 比较大的JS、CSS文件会直接被丢进磁盘,反之丢进内存
  • 内存使用率比较高的时候,文件优先进入磁盘

Service Worker

上述的缓存策略以及缓存/读取/失效的动作都是由浏览器内部判断和进行的,我们只能设置响应头的某些字段来告诉浏览器,而不能自己操作。

但 Service Worker 的出现,给予了我们另外一种更加灵活,更加直接的操作方式。我们可以选择缓存哪些文件,路由匹配规则,缓存匹配并返回。

Service Worker 借鉴了 Web Worker的 思路,即让 JS 运行在主线程之外,由于它脱离了浏览器的窗体,因此无法直接访问DOM。虽然如此,但它仍然能帮助我们完成很多有用的功能,比如离线缓存消息推送网络代理等功能。其中的离线缓存就是 Service Worker Cache

Service Worker 能够操作的缓存是有别于浏览器内部的 memory cache 或者 disk cache 的。我们可以在 Chrome 开发者工具,Application -> Cache Storage 找到缓存的位置。这个缓存是永久性的,即关闭 TAB 或者浏览器,下次打开依然还在。有两种情况会导致这个缓存中的资源被清除:手动调用 API cache.delete(resource) 或者容量超过限制,被浏览器全部清空。

如果 Service Worker 没能命中缓存,一般情况会使用 fetch() 方法继续获取资源。这时候,浏览器就去 memory cache 或者 disk cache 进行下一次找缓存的工作了。

注意:经过 Service Worker 的 fetch() 方法获取的资源,即便它并没有命中 Service Worker 缓存,甚至实际走了网络请求,也会标注为 from ServiceWorker。这个情况在后面的第三个示例中有所体现。

请求网络

如果一个请求在上述 3 个位置都没有找到缓存,那么浏览器会正式发送网络请求去获取内容。为了提升之后请求的缓存命中率,会把这个资源添加到缓存中去。具体来说:

  • 根据 Service Worker 中的 handler 决定是否存入 Cache Storage (额外的缓存位置)。

  • 根据 HTTP 头部的相关字段(Cache-control, Pragma 等)决定是否存入 disk cache

  • memory cache 保存一份资源的引用,以备下次使用。

二、HTTP 缓存

在HTTP中具有缓存功能的是浏览器缓存。HTTP的缓存属于客户端缓存,所以我们认为浏览器存在一个缓存数据库,用于储存一些不经常变化的静态文件(图片、css、js等)。

当请求一个静态资源时,浏览器会通过以下几步来获取资源:

  • 当第一次发送请求,http返回200的状态码。
  • 当后续请求该文件时候,先在本地查找该资源,如果在本地缓存找到对应的资源,但是不知道该资源是否过期或者已经过期, 则发一个http请求到服务器,然后服务器判断这个请求。
  • 如果请求的资源在服务器上没有改动过,则返回一个小的HTTP 304 Not Modeified响应, 让浏览器使用本地找到的那个资源。而如果请求的资源在服务器上已经修改过,或者这是一个新的请求(本地无对应资源),服务器则返回该资源的数据,并且返回200。当然这个是指找到资源的情况下,如果服务器上没有这个资源,则返回404Not Found 响应。

看了上述这个流程,相信大家对浏览器请求一个资源的流程有大致的了解了。接下来,我们来看一下具体的 HTTP 缓存。

HTTP 缓存属于 Disk Cache。

HTTP 缓存分类

HTTP 缓存分为:强缓存和协商缓存。

HTTP通过缓存将服务器文档的副本保留一段时间。在这段时间里,都认为文档时新鲜的,缓存可以在不联系服务器的情况下,直接提供该文档。我们称之为强缓存,此时浏览器会返回200状态码(from cache)。

但一旦以缓存副本停留的时间太长,超过了文档的新鲜度限值,就认为文档过期了。

再提供文档之前,缓存要再次与服务器进行再验证,已查看文档是否发生了变化。我们称之为协商缓存。

强缓存直接减少请求数,是提升最大的缓存策略。 它的优化覆盖了文章开头提到过的请求数据的全部三个步骤。如果考虑使用缓存来优化网页性能的话,强缓存应该是首先被考虑的。

强缓存原理

通过特殊的HTTP Expries首部(HTTP/1.0时期)和 Cache-Control首部(HTTP/1.1时期),HTTP让原始服务器向每个文档附加了一个过期日期,这些首部说明了在多长时间内可以将这些内容视为新鲜的。

浏览器第二次发送请求相同资源时,拿出过期时间和当前时间进行比较,如果在过期日期之前,则强缓存命中,如果缓存文档过期,缓存就必须与服务器进行核对,询问文档是否被修改过,如果被修改过,就要获取一份新鲜(带有新的过期日期)的副本。

强缓存首部

Expires:

在响应消息头指定一个绝对的过期日期,当再次请求时的请求时间小于返回的此时间,则直接使用缓存数据。不过由于我们可以去更改客户端的时间,因此可以更改缓存命中的结果。因此这种方式很快在后来的HTTP1.1版本中被抛弃了。故现在大多数使用Cache-Control

Expires: Thu, 10 Nov 2017 08:45:11 GMT

如果跟 cache-control 的 max-age 同时存在,Expires 会被 max-age 覆盖。

Cache-Control:

max-age

max-age=t 表示缓存内容将在t秒后失效(以秒为单位),在该时间内,客户端不需要向服务器发送请求。

跟 Expires 的区别就是前者是绝对时间,而后者是相对时间。如下:

Cache-Control: max-age=2592000

no-cache 和 no-store

no-cache 可以在本地和代理服务器缓存,但是这个缓存需要服务器验证才可以使用,即直接进入协商缓存阶段

no-store 真正意义上的“不要缓存”,不进行任何形式的缓存,每次都从服务器获取。

最佳 Cache-Control 策略:

private

客户端可以缓存,代理服务器不能缓存。这些响应通常只为单个用户缓存,因此不允许任何中间缓存对其进行缓存,例如,用户的浏览器可以缓存包含用户私人信息的HTML网页,但CDN不能缓存

public

客户端和代理服务器都可以缓存

must-revalidate

must-revalidate告诉缓存,在事先没有跟原始服务器进行再验证的情况下,不能提供这个对象的陈旧副本,缓存仍然可以随意提供新鲜的副本。如果在缓存进行must-revalidate新鲜度检查时,原始服务器不可用,缓存就必须返回一条504错误。

s-maxage

s-maxage是针对代理服务器的缓存时间

协商缓存原理

协商缓存,也叫对比缓存

本地缓存过期了并不意味着他和原始服务器目前处于活跃状态的文档有实际的区别,这只是意味着到了要进行核对的时间了,这种情况被称为协商缓存,说明缓存需要询问原始服务器是否发生变化。

  • 如果再验证显示内容发生了变化,缓存会获取一份新的文档副本,并将其存储在旧文档的位置上,然后将文档发送给客户端

  • 如果再验证内容没有发生变化,缓存只需要获取新的首部,包括一个新的过期日期,并对缓存中的首部进行更新就行了

协商缓存在请求数上和没有缓存是一致的,但如果是 304 的话,返回的仅仅是一个状态码而已,并没有实际的文件内容,因此在响应体体积上的节省是它的优化点。

协商缓存是可以和强制缓存一起使用的,作为在强制缓存失效后的一种后备方案。实际项目中他们也的确经常一同出现。

 If-Modified-Since / Last-Modified

具体流程如下:

  1. 客户端第一次向服务器发起请求,服务器将最后的修改日期(Last-Modified)附加到所提供的资源上去

  2. 当再一次请求资源时,如果没有命中强缓存,在执行再验证时,会包含一个If-Modifed-Since首部,值为资源的 Last-Modified 日期,询问服务器该资源自从这个 Last-Modified 日期之后有没有被修改过。

  3. 如果内容被修改了,服务器回送新的资源,返回200状态码和最新的修改日期

  4. 如果内容没有被修改,会返回一个304 Not Modified响应

Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT

If-None-Match / ETag

有些情况下仅使用最后修改日期进行再验证是不够的:

  • 有些文档有可能会被周期性的重写(比如: 从一个后台进程中写入),但实际上包含的数据常常是一样的,尽管内容没有变化,但修改日期会发生变化

  • 有些文档可能被修改了,但所做修改并不重要.不需要让世界范围内的缓存都重装数据(比如填写注释)

  • 有些服务器无法准确判定其页面的最后修改日期

  • 有些服务器提供的文档会在毫秒间隙发生变化(比如,实时监视器),对这些服务器来说,以一秒为粒度的修改日期可能就不够用了

因此HTTP允许用户对被称为实体标签的(ETag)的版本标识符进行比较。实体标签是附加到文档上的任意标签(引用字符串),服务器生成并返回的随机令牌通常是文件内容的哈希值或其他指纹。客户端不需要指纹是如何生成的,只需在下一次请求时将其发送至服务器。如果指纹仍然相同,则表示资源未发生变化,您就可以跳过下载。

流程和 If-Modified-Since 一致,只是 Last-Modified 字段和它所表示的更新时间改变成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 变成了 If-None-Match。服务器对比已缓存标签与服务器文档中的标签是否有所不同,相同返回 304, 不相同返回新资源和 200。

Etag 的优先级高于  Last-Modified。

但是实际应用中由于Etag的计算是使用算法来得出的,而算法会占用服务端计算的资源,所有服务端的资源都是宝贵的,所以就很少使用Etag了。

小贴士:

强制缓存和协商缓存机制可以同时存在,强制缓存的优先级高于协商缓存,当执行强制缓存时,如若缓存命中,则直接使用缓存数据库数据,不在进行缓存协商。

更新和废弃缓存

浏览器发出的所有HTTP请求会首先路由到浏览器缓存,已确认是否缓存可用于请求的有效响应。如果有匹配的响应,则从缓存中读取响应,这样就避免了网路延迟和传送产生的流量费用。

不过如果我们想更新或废弃缓存的响应,该怎么办?例如我们有一个css样式表缓存长达24小时,但是我们需要立即更新他,我们如何通知已过时的CSS缓存副本的所有访问者更新其缓存。在不更改资源网址的情况下,是做不到的。

所以,如何才能实现客户端缓存和快速更新,你可以在资源内容发生变化时,更改它的网址,强制用户下载新响应。通常情况下,可以通过在文件名中嵌入文件的指纹或版本号来实现。

  • HTML被标记为no-cache,这意味着浏览器再每次请求时都始终重新验证文档,并在内容变化时获取最新版本。此外再HTML标记内,再CSS和javascript中嵌入指纹,如果这些文件的内容发生变化,网页的HTML也会随之改变,并会下载HTML响应的新副本

  • 允许浏览器和中间缓存(例如CDN)缓存CSS,并将CSS设置为1年后到期,因为再文件名中嵌入了文件的指纹,CSS更新时网址也会随之变化

  • JavaScript同样设置为1年后到期,但标记为private,这或许是因为它包含的某些用户私人数据是CDN不应缓存的。

  • 图像缓存时不包含版本或唯一指纹,并设置为一天后到期

三、缓存小结

浏览器请求一个静态资源的完整流程:

  1. 调用 Service Worker 的 fetch 事件响应

  2. 查看 memory cache

  3. 查看 disk cache。这里又细分:

    1. 如果有强制缓存且未失效,则使用强制缓存,不请求服务器。这时的状态码全部是 200

    2. 如果有强制缓存但已失效,使用协商缓存,比较后确定 304 还是 200

  4. 发送网络请求,等待网络响应

  5. 把响应内容存入 disk cache (如果 HTTP 头信息配置可以存的话)

  6. 把响应内容的引用存入 memory cache (无视 HTTP 头信息的配置)

  7. 把响应内容存入 Service Worker 的 Cache Storage (如果 Service Worker 的脚本调用了 cache.put())

四、缓存的优点

  1. 减少了冗余的数据传递,节省宽带流量
  2. 减少了服务器的负担,大大提高了网站性能
  3. 加快了客户端加载网页的速度,这也正是HTTP缓存属于客户端缓存的原因。

五、浏览器缓存策略

默认情况下,浏览器都会使用缓存数据。

用户在浏览器操作时,会触发怎样的缓存策略。主要有 3 种:

  • 浏览器地址栏输入url 回车: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。

  • 普通刷新 (Windows: F5。Mac: Cmd+R):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。

  • 强制刷新 (Windows: Ctrl + F5。Mac: Cmd+Shift+R):会去服务器请求最新的资源文件下来。于是客户端就完成了强行更新的操作。

五、缓存的应用模式

了解了缓存的原理,我们可能更加关心如何在实际项目中使用它们,才能更好的让用户缩短加载时间,节约流量等。这里有几个常用的模式,供大家参考。

模式 1:不常变化的资源

Cache-Control: max-age=31536000

通常在处理这类资源资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,达到更改引用 URL 的目的,从而让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。

在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。如果配置中还增加 public 的话,CDN 也可以缓存起来,效果拔群。

这个模式的一个变体是在引用 URL 后面添加参数 (例如 ?v=xxx 或者 ?_=xxx),这样就不必在文件名或者路径中包含动态参数,满足某些完美主义者的喜好。在项目每次构建时,更新额外的参数 (例如设置为构建时的当前时间),则能保证每次构建后总能让浏览器请求最新的内容。

特别注意: 在处理 Service Worker 时,对待 sw-register.js(注册 Service Worker) 和 serviceWorker.js (Service Worker 本身) 需要格外的谨慎。如果这两个文件也使用这种模式,你必须多多考虑日后可能的更新及对策。

模式 2:经常变化的资源

Cache-Control: no-cache

这里的资源不单单指静态资源,也可能是网页资源,例如博客文章。这类资源的特点是:URL 不能变化,但内容可以(且经常)变化。我们可以设置 Cache-Control: no-cache 来迫使浏览器每次请求都必须找服务器验证资源是否有效。

既然提到了验证,就必须 ETag 或者 Last-Modified 出场。这些字段都会由专门处理静态资源的常用类库(例如 koa-static)自动添加,无需开发者过多关心。

也正如上文中提到协商缓存那样,这种模式下,节省的并不是请求数,而是请求体的大小。所以它的优化效果不如模式 1 来的显著。

模式 3:非常危险的模式 1 和 2 的结合 (反例)

Cache-Control: max-age=600, must-revalidate

不知道是否有开发者从模式 1 和 2 获得一些启发:模式 2 中,设置了 no-cache,相当于 max-age=0, must-revalidate。我的应用时效性没有那么强,但又不想做过于长久的强制缓存,我能不能配置例如 max-age=600, must-revalidate 这样折中的设置呢?

表面上看这很美好:资源可以缓存 10 分钟,10 分钟内读取缓存,10 分钟后和服务器进行一次验证,集两种模式之大成,但实际线上暗存风险。因为上面提过,浏览器的缓存有自动清理机制,开发者并不能控制。

举个例子:当我们有 3 种资源: index.html, index.js, index.css。我们对这 3 者进行上述配置之后,假设在某次访问时,index.js 已经被缓存清理而不存在,但 index.html, index.css 仍然存在于缓存中。这时候浏览器会向服务器请求新的 index.js,然后配上老的 index.html, index.css 展现给用户。这其中的风险显而易见:不同版本的资源组合在一起,报错是极有可能的结局。

除了自动清理引发问题,不同资源的请求时间不同也能导致问题。例如 A 页面请求的是 A.js 和 all.css,而 B 页面是 B.js 和 all.css。如果我们以 A -> B 的顺序访问页面,势必导致 all.css 的缓存时间早于 B.js。那么以后访问 B 页面就同样存在资源版本失配的隐患。

六、一些案例

光看原理不免枯燥。我们编写一些简单的网页,通过案例来深刻理解上面的那些原理。

memory cache & disk cache

我们写一个简单的 index.html,然后引用 3 种资源,分别是 index.js, index.css 和 mashroom.jpg。

我们给这三种资源都设置上 Cache-control: max-age=86400,表示强制缓存 24 小时。以下截图全部使用 Chrome 的隐身模式。

首次请求

毫无意外的全部走网络请求,因为什么缓存都还没有。

再次请求 (F5)

第二次请求,三个请求都来自 memory cache。因为我们没有关闭 TAB,所以浏览器把缓存的应用加到了 memory cache。(耗时 0ms,也就是 1ms 以内)

关闭 TAB,打开新 TAB 并再次请求

因为关闭了 TAB,memory cache 也随之清空。但是 disk cache 是持久的,于是所有资源来自 disk cache。(大约耗时 3ms,因为文件有点小)

而且对比 2 和 3,很明显看到 memory cache 还是比 disk cache 快得多的。

no-cache & no-store

我们在 index.html 里面一些代码,完成两个目标:

  • 每种资源都(同步)请求两次

  • 增加脚本异步请求图片

<!-- 把3种资源都改成请求两次 -->
<link rel="stylesheet" href="/static/index.css">
<link rel="stylesheet" href="/static/index.css">
<script src="/static/index.js"></script>
<script src="/static/index.js"></script>
<img src="/static/mashroom.jpg">
<img src="/static/mashroom.jpg">
<!-- 异步请求图片 -->
<script>setTimeout(function () {let img = document.createElement('img')img.src = '/static/mashroom.jpg'document.body.appendChild(img)}, 1000)
</script>

当把服务器响应设置为 Cache-Control: no-cache 时,我们发现打开页面之后,三种资源都只被请求 1 次。

这说明两个问题:

  • 同步请求方面,浏览器会自动把当次 HTML 中的资源存入到缓存 (memory cache),这样碰到相同 src 的图片就会自动读取缓存(但不会在 Network 中显示出来)

  • 异步请求方面,浏览器同样是不发请求而直接读取缓存返回。但同样不会在 Network 中显示。

总体来说,如上面原理所述,no-cache 从语义上表示下次请求不要直接使用缓存而需要比对,并不对本次请求进行限制。因此浏览器在处理当前页面时,可以放心使用缓存。

当把服务器响应设置为 Cache-Control: no-store 时,情况发生了变化,三种资源都被请求了 2 次。而图片因为还多一次异步请求,总计 3 次。(红框中的都是那一次异步请求)

这同样说明:

  • 如之前原理所述,虽然 memory cache 是无视 HTTP 头信息的,但是 no-store 是特别的。在这个设置下,memory cache 也不得不每次都请求资源。

  • 异步请求和同步遵循相同的规则,在 no-store 情况下,依然是每次都发送请求,不进行任何缓存。

Service Worker & memory (disk) cache

我们尝试把 Service Worker 也加入进去。我们编写一个 serviceWorker.js,并编写如下内容:(主要是预缓存 3 个资源,并在实际请求时匹配缓存并返回)

// serviceWorker.js
self.addEventListener('install', e => {// 当确定要访问某些资源时,提前请求并添加到缓存中。// 这个模式叫做“预缓存”e.waitUntil(caches.open('service-worker-test-precache').then(cache => {return cache.addAll(['/static/index.js', '/static/index.css', '/static/mashroom.jpg'])}))
})
self.addEventListener('fetch', e => {// 缓存中能找到就返回,找不到就网络请求,之后再写入缓存并返回。// 这个称为 CacheFirst 的缓存策略。return e.respondWith(caches.open('service-worker-test-precache').then(cache => {return cache.match(e.request).then(matchedResponse => {return matchedResponse || fetch(e.request).then(fetchedResponse => {cache.put(e.request, fetchedResponse.clone())return fetchedResponse})})}))
})

注册 SW 的代码这里就不赘述了。此外我们还给服务器设置 Cache-Control: max-age=86400 来开启 disk cache。我们的目的是看看两者的优先级。

当我们首次访问时,会看到常规请求之外,浏览器(确切地说是 Service Worker)额外发出了 3 个请求。这来自预缓存的代码。

第二次访问(无论关闭 TAB 重新打开,还是直接按 F5 刷新)都能看到所有的请求标记为 from SerciceWorker。

from ServiceWorker 只表示请求通过了 Service Worker,至于到底是命中了缓存,还是继续 fetch() 方法光看这一条记录其实无从知晓。因此我们还得配合后续的 Network 记录来看。因为之后没有额外的请求了,因此判定是命中了缓存。

从服务器的日志也能很明显地看到,3 个资源都没有被重新请求,即命中了 Service Worker 内部的缓存。

如果修改 serviceWorker.js 的 fetch 事件监听代码,改为如下:

// 这个也叫做 NetworkOnly 的缓存策略。
self.addEventListener('fetch', e => {return e.respondWith(fetch(e.request))
})

可以发现在后续访问时的效果和修改前是完全一致的。(即 Network 仅有标记为 from ServiceWorker 的几个请求,而服务器也不打印 3 个资源的访问日志)

很明显 Service Worker 这层并没有去读取自己的缓存,而是直接使用 fetch() 进行请求。所以此时其实是 Cache-Control: max-age=86400 的设置起了作用,也就是 memory/disk cache。但具体是 memory 还是 disk 这个只有浏览器自己知道了,因为它并没有显式的告诉我们。(个人猜测是 memory,因为不论从耗时 0ms 还是从不关闭 TAB 来看,都更像是 memory cache)

浏览器缓存场景解析

普通刷新 command + R:使用缓存资源

网页账号退出重新登录:使用缓存资源 

 强制刷新 shift + command + R:缓存清除不干净,部分用最新资源,部分用缓存

彻底清除缓存方法

 开启Chrome devtool的“停用缓存”:缓存清除彻底,全部重新加载最新资源

使用浏览器的“清空缓存并硬性重新加载”:缓存清除彻底,全部重新加载最新资源

要先开启 Chrome 的 devtool,然后右键导航栏的转圈 icon。

 使用浏览器的“清除浏览数据”:缓存清除彻底,全部重新加载最新资源

参考资料

  • 一文读懂前端缓存
  • from memory cache与from disk cache
  • 你应该知道的前端——缓存


http://chatgpt.dhexx.cn/article/drCiKcly.shtml

相关文章

一文!彻底弄懂前端缓存

前端缓存 前端缓存&#xff0c;这是一个老生常谈的话题&#xff0c;也常被作为前端面试的一个知识点。今天我们再来总结一下。 分类 前端缓存分为强缓存和协商缓存两种。 强缓存 强缓存主要使用Expires、Cache-Control 两个头字段&#xff0c;两者同时存在Cache-Control 优先级…

【前端页面缓存技术方案】

前端页面缓存技术方案 关于页面缓存数据的纯前端技术方案背景项目存在的现有方案思考&#x1f914;其他技术调研react-activationreact-router-cache-route 结论 关于页面缓存数据的纯前端技术方案 背景 为了优化用户的体验&#xff0c;可能会遇到这样的需求&#xff1a;在列…

前端常用缓存技术

http://www.cnblogs.com/belove8013/p/8134067.html 今天刚上班就听到群里的几位大佬在讨论所开发的系统需要重复的登录的恶心之处&#xff0c;听各位大佬争辩的同时&#xff0c;想到了自己以前整理过的缓存技术&#xff0c;算是比较全面的&#xff0c;当然了只是帮助自己理解的…

我的网站心得之缓存技术(前端篇)

在前端面试中&#xff0c;storage是面试官经常问的问题&#xff0c;我先问你几个问题&#xff0c;如果你回答不上来&#xff0c;那么你应该阅读一下&#xff1a;知道storage吗&#xff1f;storage存储的数据类型有什么&#xff1f;sessionStorage的生命周期&#xff1f;你都用l…

中高级前端工程师都需要熟悉的技能--前端缓存

前言 web缓存是高级前端工程师必修技能。是我们变成大牛过程中绕不开的知识点。 文章会尽量用通俗易懂的言语来细说web缓存的概念和用处。 本期文章的大纲是 什么是web缓存&#xff08;前端缓存&#xff09; 缓存可以解决什么问题&#xff1f;他的缺点是什么&#xff1f; …

ovo svm_反思我在OVO担任远程产品设计实习生的时间

ovo svm In a quiet bedroom accompanied only by the low humming of my laptop fan, I sat before a Google Hangouts meeting, and got to know my colleagues for the first time, unaware of the joy of a ride that was waiting for me at OVO Design. 在一个安静的卧室里…

反思最近这些时日的荒废

为什么80%的码农都做不了架构师&#xff1f;>>> 算是一时兴起&#xff0c;最近lol排位已经将自己的折磨的不成人样。闲了这么久&#xff0c;是时候找份工作了。最近一直没敢跟家里人打电话&#xff0c;实在不知道该说些什么&#xff0c;一开口便是谎言。是否自己真的…

团队愿景_周一的愿景,每日的成果,周五的远程团队管理反思

团队愿景 My friend J.D. Meier has an amazing blog called Sources of Insight and hes written a fantastic book called Getting Results the Agile Way. You can buy his book on Amazon (its free on Kindle Unlimited!). I put J.D. up there with David Allen and Step…

WPBeginner年满10岁-反思,更新和WordPress赠品(奖金124,000美元以上)

Wow, it’s the tenth fourth. Today, WPBeginner is officially 10 years old — feels unreal to type this! 哇&#xff0c;是第十位 今天&#xff0c;WPBeginner正式成立了10岁-键入此图标感到不切实际&#xff01; Like every year, I want to take a few minutes and d…

误泄露公司代码、疫情期间被裁,一个“菜鸟”程序员的生存日记

作者 | Adam Hughes 译者 | Sambodhi 策划 | Tina 编辑&#xff5c;燕珊 “我是如何从每一次失败中成长起来的。” 身为程序员&#xff0c;我们往往都了解大神级程序员的故事。比如很小就开始编程&#xff0c;在 11 岁时就创建了第一家能盈利的网站&#xff0c;16 岁上大学、17…

失败需要反思

2019独角兽企业重金招聘Python工程师标准>>> 公司的第一款游戏是抄袭的COC&#xff0c;然而我加入进去的时候开发已过半&#xff0c;进入之后主要是参与一些新的系统与玩法的开发&#xff0c;在我加入到这个项目之后还开发了大概半年时间&#xff0c;据说此前已经开…

CTF笔记 个人HNCTF反思(部分题目)

文章目录 [WEEK2]easy_include自己的胡思乱想WP [WEEK2]easy_unserexp [WEEK2]easy_sqlWP [WEEK2]ez_SSTIPAYLOAD [WEEK4]pop子和pipi美总结 怎么说呢&#xff0c;这次充分感觉到了自己的无能&#xff0c;可能因为在比赛马上结束的时候加入&#xff0c;让我没心思慢慢思考&…

一名大学毕业生的反思_反思我大学毕业时的软件工程师的第一年

一名大学毕业生的反思 Note: This post is mainly targeted towards students who are about to graduate or have already graduated and are preparing to start their new full-time job. Some of the examples used are specific to my experience as a New Grad Software …

华清远见嵌入式培训_第二周回顾与反思

目录 前言 周一 一、switch..case 1.1 注意事项 1.2 使用练习 二、循环控制语句 2.1 使用goto实现循环 2.2 while循环 2.3 do..while 循环 2.4 for 循环 2.5 死循环 2.6 辅助控制关键字 周二 一、数组 1.1 一维数组 1.2 数组越界问题 1.3 二维数组 1.4 编码练…

让计算机开口说话教学反思,英语教学反思(合集15篇)

英语教学反思(合集15篇) 身为一位优秀的老师&#xff0c;课堂教学是我们的工作之一&#xff0c;借助教学反思我们可以快速提升自己的教学能力&#xff0c;我们该怎么去写教学反思呢&#xff1f;以下是小编为大家收集的英语教学反思&#xff0c;希望能够帮助到大家。 英语教学反…

华清远见嵌入式培训_第五周回顾与反思

前言 这是在华清学习的第五个周末&#xff0c;这一周主要学习的是数据结构。老师说数据结构是一门非常庞大的学科&#xff0c;单单是数据结构里的一个小分支&#xff0c;单拎出来一周都未必可以学透&#xff0c;因此这周的数据结构课程里更多的是思维方向的学习&#xff0c;学习…

利用Python识别txt文本并根据其内容进行文件分类

事情是这样的&#xff0c;有一个图片数据集需要根据分成很多类以便于给其设置标签&#xff0c;但所有的图片都在一个文件里&#xff0c;另外又给了个.txt文件&#xff0c;其中每行都是对应图片的类别。例如第1行对应的第0001.jpg是第14类&#xff08;每个类都有多张图片&#x…

C# 批量修改文件名称

目的 对文件夹中所有文件名实现批量修改&#xff08;添加新字符&#xff09; 思路 获取文件夹路径获取文件夹中所有文件的文件名对文件名进行批量修改 方法 窗口设计 获取文件夹路径 使用FolderBrowserDialog控件获取文件夹路径&#xff0c;并用Directory.Exists方法对路径…

Unity Shader入门精要之Unity 提供的内置文件和变量

Unity系列文章目录 文章目录 Unity系列文章目录前言5.3.1 内置的包含文件5.3.2 内置的变量 二、Unity 提供的Cg/HLSL 语义5.5 程序员的烦恼&#xff1a;Debug5.6 小心&#xff1a;渲染平台的差异5.7 Shader 整洁之道参考 前言 上一节讲述了如何在Unity 中编写一个基本的顶点/片…

《python数学实验与建模》(10)图论模型

10.1 写出图10.20所示非赋权无向图的关联矩阵和邻接矩阵 绘制图 import networkx as nx import pylab as plt import numpy as np Anp.zeros((6,6)) List[(1,2),(1,4),(2,3),(3,4),(3,5),(3,6),(4,5),(4,6),(5,6)] for i in List:A[i[0]-1,i[1]-1]1 Gnx.Graph(A) posnx.spring…