浏览器缓存

2024/7/30 browser

​ 浏览器缓存,也称客户端缓存或 http (协议层)缓存。

​ http 缓存是一种保存资源副本并在下次请求时直接使用该副本的技术,可以复用资源,提高网站性能,减少等待时间和网络流量。Web 缓存发现请求的资源已被存储,拦截请求,返回缓存数据,不会去服务器重新下载,缓解服务器压力,提高性能。

浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。

缓存的优点:

  • 缓解服务器压力(不用每次去请求资源);
  • 提升性能(打开本地资源速度当然比请求回来再打开要快得多);
  • 减少不必要的网络传输,节约宽带(省钱)

缺点

  • 占内存(有些缓存会被存到内存中)

​ 浏览器缓存可分两大类:

① 私有缓存(也称为本地缓存或浏览器缓存)

② 共享缓存(包括多种形式的中间缓存,如代理缓存、网关缓存、CDN、反向代理缓存以及负载均衡缓存等)。

# 浏览器缓存机制

​ 强缓存优先级高于协商缓存;强缓存中 Cache-Control 优先级高于 Expires

img1.png)

  • 强缓存

    强缓存命中不会发送请求到服务端,直接从本地缓存中获取资源。返回状态码200(from memory cache/from disk cache)

    强缓存利用Cache-ControlExpires两个字段实现,表示资源在客户端的缓存有效期。

    如果浏览器判断请求的目标资源有效命中强缓存,如果命中,则可以直接从内存中读取目标资源,无需与服务器做任何通讯。

  • 协商缓存(对比缓存)

    协商缓存会发送请求到服务端,服务端通过请求头的头部字段验证是否命中协商缓存。如果命中,服务器会将这个请求返回,但是不会返回这个资源的实体,而是返回状态码304(not modified),通知浏览器从缓存中获取资源。

    协商缓存是利用Last-Modified, If-Modified-SinceETag, If-None-Match这两对字段来管理的。

强缓存与协商缓存

​ 先试图命中强缓存,再试图命中协商缓存,当协商缓存也没有命中的时候,浏览器直接从服务器加载资源数据。强缓存与协商缓存的共同点是:如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;区别是:强缓存不发送请求到服务器,协商缓存会发送请求到服务器。

# HTTP 中和缓存相关的首部字段

首部 (header):包含了很多字段,比如:cookie、缓存、报文大小、报文格式等等 主体 (body):HTTP 请求真正要传输的部分,比如:一个 HTML 文档、一个 JS 文件

​ 浏览器是否使用缓存、缓存多久,是由服务器控制的。服务器发回的响应的【响应头】部分的某些字段指明了有关缓存的关键信息。

1. 通用首部字段(请求报文与响应报文都携带)

字段 说明
Cache-Control http1.1 控制缓存行为
Pragma 优先级很高,http1.0 时的遗留字段,当值为 no-cache 时强制验证缓存,不能替代 cache-control,目前的作用是向后兼容。
服务端响应添加'Pragma': 'no-cache',浏览器表现行为和强制刷新类似。
Date 创建报文的日期时间(启发式缓存阶段会用到这个字段)

常见的 Cache-Control 字段

说明
no-store 绝对禁止缓存(禁止强缓存与协商缓存)。不存储客户端服务端任何内容,客户端都会下载完整的响应内容
no-cache 缓存但重新验证(禁止强缓存)(带本地缓存相关验证字段访问服务器,服务端验证缓存是否过期,未过期返回304,这时候缓存才使用本地缓存副本) 先验证 -> 304 -> 使用本地缓存
private 私有缓存(响应只能用于浏览器私有缓存中)中间人(CDN,代理不能缓存),如果要求 HTTP 认证,响应会自动设置为 private
public 公共缓存(可以被任何中间人缓存),多用户间共享
max-age 缓存的时长,也是响应的最大的Age值。单位:秒(s)。资源能够被缓存(保持新鲜)的最大时间;相对于Expires,是一个相对时间,优先级高于 Expires,max-age是距离请求发起的时间的秒数,针对那些不会改变的文件(静态资源),可手动设置一定的时长缓存
s-maxage 单位:秒(s)。只用于共享缓存(如:CDN缓存)。s-maxage 有效期内不请求CDN。 max-age 用于普通缓存,而 s-maxage 用于代理缓存。如果存在 s-maxage,则会覆盖掉 max-age 和 Expires
must-revalidate 缓存使用陈旧资源时必须先验证状态,已过期的缓存不被使用

Cache-Control详细参考 (opens new window)

2. 请求首部字段

字段 说明
If-Match 比较 ETag 是否一致
If-None-Match 比较 ETag 是否不一致
If-Modified-Since 比较资源最后更新时间是否一致
If-Unmodified-Since 比较资源最后更新时间是否不一致

3. 响应首部字段

字段 说明
ETag 强校验器。如果资源请求的响应头里含有 ETag,客户端可以在后续的请求的头中带上 If-None-Match 头来验证缓存

4. 实体首部字段

字段 说明
Expries http1.0 产物,实体过期时间,绝对时间,服务器返回,缓存最理想的情况,因为不会发出请求,所以后端也就无需考虑查询快慢
Last-Modified 弱校验器。资源最后一次修改的时间,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存

# 强缓存过程

​ 强缓存利用Cache-ControlExpires两个字段实现,表示资源在客户端的缓存有效期。Cache-Control 优先级高于 Expires

# 利用 Expires 字段的强缓存

​ Expires 是 http1.0 的产物,由于它是服务器返回的一个绝对时间,在服务器时间与客户端时间相差较大时,缓存管理容易出现问题。(比如:随便修改一下客户端时间,就能影响缓存命中的结果)。

​ Expires 字段的作用是,设定一个强缓存时间。在此时间范围内,则从内存(或磁盘)中读取缓存返回。比如说将某一资源设置响应头为Expires:new Date("2024-7-30 23:59:59");,那么,该资源在2024-7-30 23:59:59 之前,都会去本地的磁盘(或内存)中读取,不会去服务器请求。

  1. 浏览器第一次请求服务器的资源,服务器在返回这个资源时,在响应头中加 Expires 字段。
  2. 浏览器接受此资源,会将此资源以及响应头一起缓存 (引出: 缓存命中的请求返回的 header 不是来自服务端,而是来自之前缓存的header)。
  3. 浏览器再请求此资源时,会先从缓存中找,找到后,拿 Expires 和当前的请求时间比较,如果请求时间小于 Expires,就能命中缓存,否则没命中
  4. 如果没命中,浏览器直接从服务器加载资源时,Expires 重新加载的时候会被更新。

缺点:

  • Expires 判断强缓存是否过期的机制是:获取本地时间戳,并对先前拿到的资源文件中的 Expires 字段的时间做比较,判断是否需要对服务器发起请求。
  • 这里有一个巨大的漏洞:Expires 过度依赖本地时间,如果本地与服务器时间不同步,就会出现资源无法被缓存或者资源永远被缓存的情况。所以,Expires 字段几乎不被使用了。现在的项目中,我们并不推荐使用 Expires,强缓存功能通常使用 cache-control 字段来代替 Expires 字段。

# 利用 Cache-Control 字段的强缓存

​ Cache-control 这个字段在 http1.1 中被增加,它完美解决了Expires 本地时间和服务器时间不同步的问题,这是一个相对时间,在配置缓存的时候,以秒为单位,用数值表示。

​ Cache-control 的使用方法很简单,只要在资源的响应头上写上需要缓存多久就行,单位是秒。比如res.writeHead(200,{'Cache-Control':'max-age=10'});

  1. 浏览器第一次请求服务器的资源,服务器在返回这个资源时,在响应头中加 Cache-Control 字段。
  2. 浏览器接受此资源,会将此资源以及响应头一起缓存(引出: 缓存命中的请求返回的 header 不是来自服务端,而是来自之前缓存的header)。
  3. 浏览器再请求此资源时,会先从缓存中找,找到后,根据它第一次的请求时间和 Cache-Control 设定的有效期,计算出一个资源过期时间,再用这个过期时间和当前请求时间比较,如果请求时间小于资源过期时间,就能命中缓存,否则没命中。
  4. 如果没命中,浏览器直接从服务器加载资源时,Cache-Control 重新加载的时候会被更新。

优势:

  • Cache-Control 描述的是一个相对时间,在进行缓存命中的时候,都是利用客户端时间进行判断,不需要比对客户端和服务端的时间,解决了Expires所存在的巨大漏洞。所以相比较 Expires,Cache-Control 的缓存管理更有效、安全一些。

​ 没有命中强缓存就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的 http 状态为 304 并且会显示一个 Not Modified 的字符串。

# 协商缓存过程

​ 协商缓存是利用的是Last-Modified, If-Modified-SinceETag, If-None-Match这两对字段来管理的。

# Last-Modified & If-Modified-Since 控制的协商缓存

基于 Last-Modified 的协商缓存实现方式是:

  1. 首先需要在服务器端读出文件修改时间,
  2. 将读出来的修改时间赋给响应头的last-modified字段。
  3. 最后设置Cache-control:no-cache

三步缺一不可。

​ 当客户端读取到 last-modified 时,会在下次的请求标头中携带一个字段:If-Modified-Since。之后每次对该资源的请求,都会带上 If-Modified-Since 这个字段,而服务端就需要拿到这个时间并再次读取该资源的修改时间,比对它们两者,来决定是读取缓存还是返回新的资源。

  1. 浏览器第一次请求服务器的资源,服务器在返回这个资源时,在响应头中加 Last-Modified 字段,表示这个资源在服务器上的最后修改时间
  2. 浏览器再次请求此资源时,会在请求头加 If-Modified-Since 字段,值等于上次请求时的 Last-Modified 的值。
  3. 服务器再次收到资源请求时,会根据请求头的 If-Modified-Since 和资源在服务器上的最后修改时间判断资源是否有变化。如果没有变化,返回 304(Not Nodified),但不会返回资源内容;如果有变化,则正常返回资源。当服务器返回304的响应时,响应头不会再添加 Last-Modified ,因为资源没有变化,那么 Last-Modified 也不会变化,即不更新 Last-Modified。
  4. 浏览器收到 304 响应后,就会从缓存中加载资源。
  5. 如果协商缓存没有命中,浏览器直接从服务器加载,Last-Modified 会被更新,下次请求时,If-Modified-Since 会使用上次返回的 Last-Modified 。

Last-Modified 的弊端

  • 如果本地打开缓存文件,即使没有对文件进行修改(文件内容没有变化),但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源。(因为是根据文件修改时间来判断的,所以,在文件内容本身不修改的情况下,依然有可能更新文件修改时间,比如修改文件名再改回来,这样,就有可能文件内容明明没有修改,但是缓存依然失效了。)
  • 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源。(因为文件修改时间记录的最小单位是秒,所以,如果文件在几百毫秒内完成修改的话,文件修改时间不会改变,这样,即使文件内容修改了,依然不会返回新的文件。)
  • 有些服务器无法精准获取文件的最后修改时间

​ Last-Modified & If-Modified-Since 都是服务端响应头返回的字段,一般来说,在没有调整服务器时间和篡改客户端缓存的情况下,这两个字段配合起来管理协商缓存是非常可靠的,但是有时候会存在服务器上资源其实有变化,但是最后修改时间却没有变化的情况,而这种问题又很不容易被定位出来,而当这种情况出现的时候,就会影响协商缓存的可靠性。为了解决上述的这两个问题,从 http1.1 开始,就有了另外一对ETag, If-None-Match来管理协商缓存。

# ETag & If-None-Match 控制的协商缓存

ETag 就是将原先协商缓存的比较时间戳的形式修改成了比较文件指纹。

文件指纹:根据文件内容计算出的唯一哈希值。文件内容一旦改变则指纹改变。

基于 ETag 的协商缓存实现方式是:

  1. 第一次请求某资源的时候,服务端读取文件并计算出文件指纹,将文件指纹放在响应头的ETag 字段中跟资源一起返回给客户端。
  2. 第二次请求某资源的时候,客户端自动从缓存中读取出上一次服务端返回的 ETag 也就是文件指纹。并赋给请求头的 if-None-Match 字段,让上一次的文件指纹跟随请求一起回到服务端。
  3. 服务端拿到请求头中的 is-None-Match 字段值(也就是上一次的文件指纹),并再次读取目标资源并生成文件指纹,两个指纹做对比。如果两个文件指纹完全吻合,说明文件没有被改变,则直接返回304状态码和一个空的响应体并 return。如果两个文件指纹不吻合,则说明文件被更改,那么将新的文件指纹重新存储到响应头的 ETag 中并返回给客户端
  1. 浏览器第一次请求服务器的资源,服务器在返回这个资源时,在响应头中加 ETag 字段,ETag 是服务器根据当前请求的资源生成的一个唯一标识,这个唯一标识是一个字符串,只要资源有变化这个串就会不同,跟最后修改时间没有关系,所以能很好的补充 Last-Modified 的问题。
  2. 浏览器再次请求此资源时,会在请求头加 If-None-Match 字段,值等于上次请求时的 ETag 的值。
  3. 服务器再次收到资源请求时,会根据请求头的 If-None-Match 和 服务端再根据资源生成的新的 ETag 比较,如果没有变化返回 304(Not modified),但是不会返回资源的内容;如果有变化,就正常返回资源。与 Last-Modified 不一样的是,当服务器返回 304 的响应时,由于 ETag 重新生成过,响应头中还会把这个 ETag 返回,即使这个 ETag 跟之前的没有变化
  4. 浏览器收到 304 响应后,就会从缓存中加载资源。

缺点:

  • ETag 需要计算文件指纹,这意味着服务端需要更多的计算开销,如果文件尺寸大,数量多,并且计算频繁,那么 ETag 的计算就会影响服务器的性能。在这样的场景下就不是很适合。
  • ETag 有强验证和弱验证,所谓将强验证,ETag 生成的哈希码深入到每个字节,哪怕文件中只有一个字节改变了,也会生成不同的哈希值,它可以保证文件内容绝对的不变。但是,强验证非常消耗计算量。ETag 还有一个弱验证,弱验证是提取文件的部分属性来生成哈希值,因为不必精确到每个字节,所以他的整体速度会比强验证快,但是准确率不高,会降低协商缓存的有效性。

​ Etag 和 Last-Modified 非常相似,都是用来判断一个参数,从而决定是否启用缓存。但是 ETag 相对于Last-Modified 也有其优势,可以更加准确的判断文件内容是否被修改,从而在实际操作中实用程度也更高。如果两个都支持的话,服务器会优先选择 ETag。

值得注意的一点是,不同于 cache-control 是 expires 的完全替代方案(说人话:能用 cache-control 就不要用 expiress)。ETag 并不是 last-modified 的完全替代方案,而是last-modified的补充方案(说人话:项目中到底是用 ETag 还是 last-modified 完全取决于业务场景,这两个没有谁更好谁更坏)。

​ 协商缓存跟强缓存不一样,强缓存不发请求到服务器,所以有时候资源更新了浏览器还不知道,但是协商缓存会发请求到服务器,所以资源是否更新,服务器肯定知道。大部分web服务器都默认开启协商缓存,而且是同时启用Last-Modified & If-Modified-SinceETag & If-None-Match,同时开启是为了处理 Last-Modified 不可靠的问题。

协商最佳实践

  1. 如果使用 Last-Modified 不会出现任何问题,可以直接移除 ETag,google 的搜索首页则没有使用 ETag。
  2. 确定要使用 ETag,在配置 ETag 的值的时候,移除可能影响到组件集群服务器验证的属性,例如只包含组件大小和时间戳。

注意

分布式系统里多台机器间文件的 Last-Modified 必须保持一致,以免负载均衡到不同机器导致比对失败。
分布式系统尽量关闭掉 ETag(每台机器生成的 ETag 都会不一样)。

对比

  • 在精准度上,ETag 优于 Last-Modified。优于 ETag 是按照内容给资源上标识,因此能准确感知资源的变化。而 Last-Modified 就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况:
    • 编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效。
    • Last-Modified 能够感知的单位时间是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last-Modified 并没有体现出修改了。
  • 在性能上,Last-Modified 优于 ETag,也很简单理解,Last-Modified 仅仅只是记录一个时间点,而 Etag 需要根据文件的具体内容生成哈希值。

# 缓存实践

​ 缓存的意义就在于减少请求,更多地使用本地的资源,给用户更好的体验的同时,也减轻服务器压力。所以,最佳实践,就应该是尽可能命中强缓存,同时,能在更新版本的时候让客户端的缓存失效

​ 在更新版本之后,如何让用户第一时间使用最新的资源文件呢?机智的前端们想出了一个方法,在更新版本的时候,顺便把静态资源的路径改了,即,可以利用 webpack 的文件指纹策略。这样,就相当于第一次访问这些资源,就不会存在缓存的问题了。

​ 一个较为合理的缓存方案:

  • HTML:使用协商缓存。
    • 假设 html 使用了强缓存,当我们重新上线 css、js等文件,html文件(因为css改变了,所以html要重新引入css文件,因此html也要改并且重新上线),但是 html 使用的是强缓存,在缓存期间浏览器直接从强缓存中读取html文件,以至于你重新上线了但是还是原来的 html,内容还是原来,必须要用户强制刷新才会有新内容出现,这就不合理。所以不能使用强缓存。
  • CSS、JS、图片:使用强缓存,且文件命名带上 hash 值。
    • 更新资源发布路径以实现非覆盖式发布,使强缓存失效。
    • 利用版本号更新策略可行吗?<link rel="stylesheet" href="/index.css?v=1.0.2">在后面加个版本号,因为每次 html 都会发送请求询问是否过期,所以就可以达到缓存更新效果。但是开发不可能就引用一个 css 文件,往往是多个的。每次修改一个css 文件,其余的版本也要跟着升级,没有必要。

​ 既然可以使用协商缓存的 Etag,那有必要使用文件指纹吗?

  • 缓存的意义。缓存的意义就在于减少请求,更多地使用本地的资源,给用户更好的体验的同时,也减轻服务器压力。所以,最佳实践,就应该是尽可能命中强缓存,同时,能在更新版本的时候让客户端的缓存失效,访问最新资源,那么就需要文件指纹、版本号等使强缓存失效的手段。
  • CSS、js、图片、字体等资源要做强缓存。利用 Webpack 合理分包加上文件指纹做到客户端的持久化缓存,不去解析 DNS,不去请求服务器,减小服务端压力的同时提高用户体验。所以,文件指纹用于资源路径更新使缓存失效,达到及时拉取最新资源的目的,使用文件指纹和 Etag 没什么关系。
  • 分布式服务不使用 Etag。分布式服务,Etag 是关闭的,因为每台机器生成的 ETag 都会不一样。
  • 持久换缓存做无覆盖式更新。CSS、js、图片、字体等资源不需要做协商缓存,不需要每次请求服务器询问资源是否新鲜。只需要 hrml 做协商缓存即可。若发布了最新资源,由于 html 中引用的资源的路径变了,会拉取最新资源,做无覆盖式更新。

​ 总之一句话,为减少服务器请求,资源要尽可能做强缓存,而资源的更新需要利用文件指纹变更资源路径使强缓存失效,从而达到及时拉取到最新资源的目的。所以,文件指纹是为强缓存资源服务的,与协商缓存 Etag 无任何关系。

# 浏览器缓存流程图

​ 浏览器缓存的全过程如下图所示:

img

​ 具体来说,这个过程可以进行进一步的划分,细化后的流程图如下所示:

浏览器缓存流程

# 浏览器缓存位置

​ 浏览器缓存从存储位置上主要分为四种类型,每种类型都有其特定的作用和优先级。当浏览器按照顺序检查这些缓存位置且均未命中时,才会发起网络请求。

# 1. Service Worker Cache

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

​ Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。

​ 当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。

特点

  • 持久性:缓存持久存在,不受页面关闭影响。
  • 自定义缓存策略:开发者可以自由控制哪些资源被缓存,如何匹配和读取缓存。
  • HTTPS要求:由于涉及请求拦截,必须使用HTTPS协议。

工作流程

  1. 注册Service Worker。
  2. install事件处理器中缓存所需资源。
  3. 拦截网络请求,检查Service Worker缓存中是否存在所需资源,若存在则直接返回缓存内容,否则发起网络请求。

# 2. Memory Cache

​ Memory Cache,就是内存中的缓存,存储了当前页面已加载的资源,如样式表、脚本和图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

​ 内存缓存中有一块重要的缓存资源是 preloader 相关指令(例如<link rel="prefetch">)下载的资源。 preloader 的相关指令已经是页面优化的常见手段之一,它可以一边解析 js/css 文件,一边网络请求下一个资源。

​ 需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的 HTTP 缓存头 Cache-Control 是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对 Content-Type,CORS 等其他特征做校验。

特点

  • 读取速度快:直接从内存中读取资源,速度远快于磁盘。
  • 短暂性:缓存生命周期与页面进程绑定,页面关闭即释放。
  • 复杂匹配逻辑:缓存匹配不仅基于URL,还可能考虑Content-Type、CORS等。

应用场景

  • 页面内资源的快速访问。
  • 预加载(如使用<link rel="prefetch">)资源的缓存。

# 3. Disk Cache

​ Disk Cache,就是存储在硬盘中的缓存,提供更大的容量和更长的持久性。它会根据 header 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache。比较大的JS、CSS文件会直接被丢进磁盘,反之丢进内存;内存使用率比较高的时候,文件优先进入磁盘。

特点

  • 大容量:能够存储大量资源。
  • 持久性:缓存内容在跨会话时仍然有效。
  • 智能缓存策略:根据HTTP头部信息(如Cache-Control)决定资源的缓存和验证策略。

应用场景

  • 大多数网络资源的缓存。
  • 跨会话的资源重用。

# 4. Push Cache

​ Push Cache 是 HTTP/2 协议引入的一种缓存机制,作为最后一道缓存屏障。当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在 Chrome 浏览器中只有 5 分钟左右,同时它也并非严格执行 HTTP 头中的缓存指令。

特点

  • 会话级缓存:仅在单个HTTP/2会话期间有效。
  • 短暂性:缓存时间非常短,如Chrome中仅持续几分钟。
  • 非严格缓存策略:不完全遵循HTTP缓存头部指令。

应用场景

  • 当其他缓存均未命中时,作为后备缓存使用。

# 用户操作行为影响

​ 用户的操作行为会直接影响浏览器对网页资源的缓存和验证策略,不同的操作可能导致浏览器采取不同的策略来获取资源,从而影响网页的加载速度和性能。

行为 Expires/Cache-Control Last-Modified/Etag
地址栏回车
页面链接跳转
新开窗口
前进回退
F5 刷新
Ctrl + F5 强制刷新
  • 地址栏回车:

    Expires/Cache-Control:✅ 如果请求的URL在浏览器缓存中存在且未过期(根据Expires或Cache-Control头部),则浏览器会直接使用缓存中的资源,而不会向服务器发送请求。

    Last-Modified/Etag:✅ 如果缓存资源已经过期,或者浏览器决定不使用缓存(比如因为缓存策略或缓存空间不足),浏览器会向服务器发送请求,并在请求中携带Last-Modified或Etag等验证头部,以检查资源自上次请求以来是否被修改过。

  • 页面链接跳转:

    ✅与地址栏回车类似,页面链接跳转也会根据Expires/Cache-Control头部判断是否使用缓存,并在需要时通过Last-Modified/Etag进行验证。

  • 新开窗口:

    ✅新开窗口加载网页时,同样会遵循缓存和验证机制。浏览器会先检查缓存,然后根据需要向服务器发送请求并可能进行资源修改验证。

  • 前进回退:

    ✅在浏览器的前进和回退操作中,由于这些页面之前已经被加载并缓存,所以通常会直接使用缓存中的版本,而不会向服务器发送请求(除非缓存已过期或失效)。

  • F5 刷新:

    Expires/Cache-Control:✅ F5刷新会考虑缓存策略,但会绕过常规的缓存验证过程(如Last-Modified/Etag),直接向服务器发送请求以获取最新资源。

    Last-Modified/Etag:❎ 在F5刷新时,浏览器不会发送这些验证头部来检查资源是否已被修改。它直接请求服务器返回资源的最新版本。

  • Ctrl + F5 强制刷新:

    Expires/Cache-Control:❎ 强制刷新会忽略所有的缓存策略,直接向服务器请求资源的最新版本。

    Last-Modified/Etag:❎ 在强制刷新时,由于直接请求最新版本,所以也不会发送Last-Modified/Etag等验证头部。

# 总结

浏览器缓存

# 问题

# Q1. no-cache和no-store 以及 max-age=0 的区别

Cache-controlmax-ages-maxageno-cacheno-storeprivatepublic这六个属性。

属性 介绍
max-age 决定客户端资源被缓存多久
s-maxage 决定代理服务器缓存的时长
no-cache 表示强制进行协商缓存(强制源服务器再次验证),跳过当前的强缓存,发送HTTP请求,即直接进入协商缓存阶段
no-store 表示禁止任何缓存策略,非常粗暴,不进行任何形式的缓存
public 表示资源即可以被浏览器缓存也可以被代理服务器缓存
private 表示资源只能被浏览器缓存,中间的代理服务器不能缓存
  • no-cache

Cache-Control 为 no-cache。表示必须重新去获取请求。由服务端来判断是否使用缓存,即可以在本地缓存,可以在代理服务器缓存,但是这个缓存要服务器验证才可以使用。

  • no-store

是真正的不进行缓存。即彻底得禁用缓存,本地和代理服务器都不缓存,每次都从服务器获取。

  • max-age=0
    • max-age > 0 时。直接从浏览器缓存中提取。
    • max-age <= 0 时。向服务端发送 http 请求确认,该资源是否有修改, 有的话返回 200 。没有返回 304。(即 max-age=0 表示不管 response 怎么设置,在重新获取资源之前,先检验 ETag/Last-Modified)

no_cache 是 Cache-control 的一个属性,它并不像字面意思一样禁止缓存,而是强制进行协商缓存。如果某一资源的 Cache-control 中设置了 no-cache,那么该资源会直接跳过强缓存的校验,直接去服务器进行协商缓存。而 no-store 是禁止所有的缓存策略。

注意:no-cache和no-store是一组互斥属性,这两个属性不能同时出现在Cache-Control中。

# Q2. 状态码为 200 from cache 和 304 Not modified 的区别

  • 请求状态码为 200 from cache

表示该资源已经被缓存过,并且在有效期内,所以不再向浏览器发出请求,直接使用本地缓存。

  • 状态码为 304 Not modified

表示浏览器虽然发现了本地有该资源的缓存,但是不确定是否是最新的,于是想服务器询问,若服务器认为浏览器的缓存版本还可用(即还未更新),那么便会返回 304,继续使用本地的缓存。

参考资料:

前端缓存(浏览器缓存和http缓存)详解_前端浏览器缓存-CSDN博客 (opens new window)

[(1.6w字)浏览器灵魂之问,请问你能接得住几个?作为一个合格的前端工程师,浏览器相关的工作原理是我们进行性能优化的基石 - 掘金 (juejin.cn) (opens new window)](https://juejin.cn/post/6844904021308735502#heading-24)

浏览器的缓存机制-彻底理解强缓存与协商缓存看这篇就够了 - ____chen - 博客园 (cnblogs.com) (opens new window)

上次更新: 2024/8/17 02:25:10