关于浏览器缓存(HTTP 强缓存+协商缓存)、浏览器强制刷新的一些记录,常记常新

1,224 阅读6分钟

前言

午睡起,项目里开了个需求会,讲到了缓存这个事,后端说"这些数据存在后端没什么意义,让前端做本地缓存吧"。突然脑光一闪,写个浏览器缓存吧,温故而知新。

本文主要讲 HTTP 强缓存+协商缓存,和 浏览器的强制刷新

什么是浏览器缓存

当我们访问一个网站的时候,会去加载各种资源,包括 HTML、JS、CSS、图片等,浏览器会将一些不经常变动的资源缓存在本地,当下次访问时,直接从本地加载资源,并不通过请求服务器,这就是浏览器缓存。

所以,合理利用缓存,缩短白屏时间(FCP),提高页面的打开速度,减少服务器端的压力。

有哪些缓存类别

Service Worker

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

Memory Cache、Disk Cache

当我们打开一个新页面时,会发现某些资源是 from Memory Cache的,但是在刷新页面后,就变成了from Disk Cache

Memory Cache 是内存中的缓存 , Disk Cache 是磁盘中的缓存。

强缓存 + 协商缓存

前端最常用的缓存方案:强缓存+协商缓存

  • 入口文件都用协商缓存,如: HTML。
  • JS、CSS、图片等资源配置,用强缓存。

此方案的好处:当项目版本更新时,协商缓存可以及时获取到最新的页面;若版本未变化,可继续复用之前的缓存资源;既很好利用了浏览器缓存,又解决了页面版本更新的问题。

当html文件获取到最新的资源,且该资源里关联了其他的hash的文件,浏览器会下载新的,不会走到之前的缓存。

HTTP 强缓存+协商缓存

强缓存:如果资源没过期,就取缓存,如果过期了,则请求服务器,一般用于JS、CSS、图片等资源。

协商缓存:每次请求资源的时候,都会跟服务器协商,目标资源是否过期,如果没有过期则返回304状态码,客户端复用本地资源,如果过期了则返回200状态码,返回目标资源。

强缓存

当我们访问页面,浏览器会根据服务器返回的 response Header 来判断是否对资源进行缓存,如果响应头中有 cache-controlexpires 字段,代表该资源是强缓存。

image.png

Expires

Expires 值为一个时间戳, 绝对时间 ,服务器返回该资源缓存的到期时间。这样会存在一个缺陷:这个绝对时间是相对于系统的本地时间来比较的,但是系统的本地时间是可以人为去更改的,所以这让它有了不准确性。

为了弥补这一缺陷,到了HTTP/1.1,Expire 已经被 Cache-Control 替代。

Cache-Control

Cache-Control 使用了 max-ag 相对时间,如果 max-age = 31536000,代表在 31536000 秒后该资源过期,如果未超过过期时间,浏览器会直接使用缓存结果,强制缓存生效。

Cache-Control 的取值为:

  • public:资源客户端和服务器都可以缓存
  • privite:资源只有客户端可以缓存
  • no-cache:协商缓存的标志
  • no-store:不使用缓存
  • max-age:缓存保质期,是 相对时间

「Cache-Control: no-cache 和 no-store的区别:」

  • Cache-Control: no-cache 是会被缓存的,是协商缓存的标识,只不过每次都会向服务器发起请求,来验证当前缓存的有效性。
  • Cache-Control: no-store 这个才是响应不被缓存的意思。

注意:当 Cache-Controlexpires 两者都存在时,Cache-Control 优先级更高

协商缓存

协商缓存重要的两个字段是:Last-ModifiedETag

image.png

Last-Modified

资源在服务器最后被修改的时间,从服务器 Respnse Headers 上获取。

  • 第一次访问页面时,服务器的响应头会返回 Last-Modified 字段。
  • 客户端再次发起该请求时,请求头 If-Modified-Since 字段会携带上次请求返回的 Last-Modified 值。
  • 服务器根据 If-Modified-Since 的值,与该资源在服务器最后被修改时间做对比,若服务器上的时间大于 Last-Modified 的值,则重新返回资源,返回 200,表示资源已更新;反之则返回 304,代表资源未更新,可继续使用缓存。

ETag

当前资源文件的一个唯一标识(由服务器生成),从请求响应头中获取,若文件内容发生变化该值就会改变。

  • 第一次访问页面时,服务器的响应头会返回 etag 字段。
  • 客户端再次发起该请求时,请求头 If-None-Match 字段会携带上次请求返回的 etag 值。
  • 服务器根据 If-None-Match 的值,与该资源在服务器的 Etag 值 做对比,若值发生变化,状态码为200,表示资源已更新;反之则返回 304,代表资源无更新,可继续使用缓存。

有了 Last-Modified, 为什么还需要 ETag

Etag 的出现主要是为了解决一些 Last-Modified 难处理的问题:

1)一些文件也许会周期性的更改,但是内容并不改变,这时候并不希望客户端认为这个文件被修改了而重新去请求;

2)某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说 1s 内修改了 N 次),If-Modified-Since 能检查到的粒度是 秒级 的,使用 Etag 就能够保证这种需求下客户端在 1 秒内能刷新 N 次 cache。

注意:Etag 优先级高于 Last-Modified,若 EtagLast-Modified 两者同时存在,服务器优先校验 Etag

浏览器缓存流程

image.png

浏览器的强制刷新

image.png

正常重新加载:按照配置的请求头正常请求资源,该取缓存就取缓存。

image.png

硬性重新加载:即强制刷新,可以看到以下结果,Chrome Devtools中请求头显示 Cache-Control:max-age=31536000, 是强缓存,按理说刚缓存过了,这次就应该依然取缓存才对,但是为什么缓存失效了呢?

image.png

image.png

我们在用 charles 抓包工具,强制刷新抓取该请求看一下:

image.png

可以看到该请求的 Cache-Control 变成 no-cache了,也就是变成协商缓存,需要跟服务端确认是否更新本地缓存,这就是强制刷新的原理

所以说明了:Chrome Devtools 隐藏了这个行为。停用缓存的功能也是通过设置 Cache-Controlno-cache 来实现的。

清空缓存并硬性加载:就是清空本地的缓存,让服务端返回最新的资源。

总结

  • 浏览器缓存类别:Service Worker、 Memory Cache、Disk Cache、强缓存 + 协商缓存。
  • 强缓存:通过 expires 或 cache-control:max-age 字段, 来判断是否失效。
  • 协商缓存:通过 Last-Modified 和 ETag 字段,来判断资源是否失效。
  • 浏览器的强制刷新,是通过改变 Cache-Control 为 no-cache 来实现的,禁用缓存功能也是如此。