了解前端缓存

分类:技术最近更新:2026-04-15浏览:1467

了解前端缓存

前端/后端缓存

在开发过程中,经常会提到“缓存”一词,不管是前端还是后端,缓存的主要作用就是缩短请求和相应时间,增强用户体验。在理解前后端缓存之前,我们先查看通常的网络请求:

image.png

基本的网络请求就是三个步骤:请求,处理,响应。

在第二步,pedding状态就表示前端在等待服务端处理询问的任务。针对读取的数据,如果有不经常更改的特性,后端经常会把这些数据进行缓存,不去数据库读取,直接从redis或者内存中读取,以实现快速完成响应,缩短响应周期。具体这边不做过多的介绍。可以阅读:https://zhuanlan.zhihu.com/p/32434005

前端哪里缓存了

本文主要介绍前端是如何缓存的。上图中,排除第二步,就剩下询问和响应了,分别对应了request和response。

我们在日常请求中会出现下图所示的请求,我们从time请求时长可以看出size为memory cache 和disk cache 请求的时间为比较短,甚至为0。

blogfile/editor/202208/1660823479980921575.png

我们再看请求头,似乎和缓存相关的配置看不到。

WX20220818-195213.png

再看看响应头消息,我们可以看到Cache-controldatelast-modifiedETagmax-age等信息。

WX20220818-195240.png

通过整理我们可以得到以下知识点:

  • 按缓存位置分类:memory cachedisk cacheService WorkerPush Cache
  • 按失效策略分类:Cache-ControlETag

我们先从缓存位置分类,逐个了解几个类型。

memory cache

memory cache 是内存中的缓存,(与之相对 disk cache 就是硬盘上的缓存)。按照操作系统的常理:先读内存,再读硬盘。几乎所有的网络请求资源都会被浏览器自动加入到 memory cache 中。但是也正因为数量很大但是浏览器占用的内存不能无限扩大这样两个因素,memory cache 注定只能是个短期存储。常规情况下,浏览器的 TAB 关闭后该次浏览的 memory cache 便告失效 (为了给其他 TAB 腾出位置)。而如果极端情况下 (例如一个页面的缓存就占用了超级多的内存),那可能在 TAB 没关闭之前,排在前面的缓存就已经失效了。

优点:

  • 读取速度快

缺点:

  • 空间有限,一旦关闭 Tab 页面,内存中的缓存也就被释放了。

如何触发:

  • 因为tab页面关闭就消失,所以新标签页不会有memory cache,只有在当前tab再次刷新页面才生效。
  • base64解析的图片会直接从memory cache 读取,不管清除缓存记录还是关闭tab标签页。
  • <link rel="preload"> 指定的预加载资源,也会被放入 memory cache

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

在从 memory cache 获取缓存内容时,浏览器会忽视例如 max-age=0no-cache 等头部配置。例如页面上存在几个相同 src 的图片,即便它们可能被设置为不缓存,但依然会从 memory cache 中读取。这是因为 memory cache 只是短期使用,大部分情况生命周期只有一次浏览而已。而 max-age=0 在语义上普遍被解读为“不要在下次浏览时使用”,所以和 memory cache 并不冲突。也就是说,即便你设置了no-cache,当用户不关闭你的网页窗口,你代码更新,当前页面刷新依旧不读取最新代码,所以这个时候要让客户关闭打开,或者当前页面强制刷新。

知识扩展:no-cache、no-store区别

如果你连短期都不能容忍,可以配置nginx:

bash
location / { add_header Cache-Control no-store; }

no-cache从字面意义上很容易误解为不缓存,但是no-cache代表不缓存过期的资源,缓存会向服务器进行有效处理确认之后处理资源,使用no-cache的目的就是为了防止从缓存中获取过期的资源。

no-store才是真正的不进行缓存

disk cache

disk cache 也叫 HTTP cache,顾名思义是存储在硬盘上的缓存,因此它是持久存储的,是实际存在于文件系统中的。而且它允许相同的资源在跨会话,甚至跨站点的情况下使用,例如两个站点都使用了同一张图片。

disk cache 会严格根据 HTTP 头信息中的各类字段来判定哪些资源可以缓存,哪些资源不可以缓存;哪些资源是仍然可用的,哪些资源是过时需要重新请求的。当命中缓存之后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求还是快了不少的。绝大部分的缓存都来自 disk cache。

关于 HTTP 的协议头中的缓存字段,我们会在稍后进行详细讨论。

优点:

  • 缓存存在硬盘中,容量大

缺点:

  • 读取速度慢

如何触发:

  • 根据浏览器请求头

扩展:

  • 浏览器会把哪些文件丢进内存中?哪些丢进硬盘中?
  • 关于这点,网上说法不一,不过以下观点比较靠得住:
    • 对于大文件来说,大概率是不存储在内存中的,反之优先
    • 当前系统内存使用率高的话,文件优先存储进硬盘

Service Worker

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

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。

  • 传输协议必须为 HTTPS
  • Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
  • Service Worker 不常用

Push Cache

Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。

  • 它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂
  • Push Cache 也不常用

缓存优先级

Service WorkerMemory CacheDisk Cache ➔ 向服务器请求

  1. 先去内存看,如果有,直接加载
  2. 如果内存没有,则取硬盘获取,如果有直接加载
  3. 如果硬盘也没有,那么就进行网络请求
  4. 加载到的资源缓存到硬盘和内存

访问图片为例: 访问 -> 200 -> 退出浏览器 -> 再进来 -> 200(from disk cache) -> 刷新 -> 200(from memory cache)


刚刚我们通过位置了解了几个类型,下面再通过失效策略分类了解缓存机制。 在失效策略分类中,又分为:强制缓存(强缓存)协商缓存(对比缓存)

强制缓存 (也叫强缓存)

不会向服务器发送请求,直接从缓存中读取资源。

在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cachefrom memory cache

强缓存可以通过设置两种 HTTP Header 实现:ExpiresCache-Control

Expires

这是 HTTP 1.0 的字段,表示缓存到期时间,是一个绝对时间 (当前时间+缓存时间),示例:

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

在响应消息头中,设置这个字段之后,就可以告诉浏览器,在未过期之前不需要再次请求。

但是,这个字段设置时有两个缺点:

  1. 由于是绝对时间,用户可能会将客户端本地的时间进行修改,而导致浏览器判断缓存失效,重新请求该资源。此外,即使不考虑自行修改,时差或者误差等因素也可能造成客户端与服务端的时间不一致,致使缓存失效。
  2. 写法太复杂了。表示时间的字符串多个空格,少个字母,都会导致非法属性从而设置失效。
  3. 如果max-ageExpires同时存在,则被Cache-Controlmax-age覆盖。

Cache-control

已知Expires的缺点之后,在HTTP/1.1中,增加了一个字段Cache-control,该字段表示资源缓存的最大有效时间,在该时间内,客户端不需要向服务器发送请求。

配置示例:

Cache-control: max-age=2592000

Cache-control 常用值说明:

  • max-age:最大有效缓存时间
  • must-revalidate:如果超过了 max-age 的时间,浏览器必须向服务器发送请求,验证资源是否还有效。
  • no-cache:缓存内容,但必须向服务端验证有效性后再使用
  • no-store: 真正意义上的禁止所有缓存,不走强制缓存、协商缓存
  • public:所有的内容都可以被缓存 (包括客户端和代理服务器, 如 CDN)
  • private:所有的内容只有客户端才可以缓存,代理服务器不能缓存,默认值。

扩展:

max-age是HTTP/1.1中,指web文件被用户访问(请求)后的存活时间,是个相对值,相对请求时间(Request_time)。 例:18:00发布,Expires和max-age都设置10分钟过期; 18:05再次请求页面,18:11刷新时,只有Expires缓存过期更新,max-age需要18:15之后才会更新。

max-age=0no-cache 等价吗? 从规范字面意思:max-age到期需要重新验证,no-cache是必须重新验证; 实际浏览器表现中,二者行为基本一致; max-age=0, must-revalidate 则和 no-cache 完全等价。

总结: 自从 HTTP/1.1 开始,Expires 逐渐被 Cache-control 取代。

  • Cache-control 是相对时间,不受客户端本地时间修改影响
  • 配置灵活、兼容性更强
  • Cache-control 优先级高于 Expires
  • 生产环境为兼容 HTTP/1.0 + HTTP/1.1,通常会同时配置两个字段

协商缓存 (也叫对比缓存)

强制缓存失效时,启用协商缓存,由服务器最终判定缓存是否失效。

核心流程: 浏览器携带缓存标识请求服务端 ➔ 服务端对比标识:

  • 资源无更新:返回 304 状态码,客户端继续使用本地缓存
  • 资源已更新:返回 200 状态码 + 最新资源 + 全新缓存规则

对比缓存请求次数和无缓存一致,但304响应无响应体,极大减少传输体积,节省带宽与响应时间; 协商缓存是强缓存的兜底方案,项目中通常搭配使用。

协商缓存分为两组实现方案:

  1. Last-Modified & If-Modified-Since
  2. Etag & If-None-Match

Last-Modified & If-Modified-Since

  1. 服务器通过 Last-Modified 响应头,返回资源最后修改时间
    Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT
    
  2. 浏览器缓存该时间标识
  3. 下次请求资源时,在请求头携带 If-Modified-Since 带上上次的修改时间
  4. 服务端对比时间:
    • 一致:资源未修改,返回 304
    • 不一致:资源已更新,返回 200 + 新资源

存在缺陷:

  1. 时间精度仅到秒,秒级内频繁更新的资源无法精准识别
  2. 动态生成文件,修改时间会实时刷新,内容无变化也会重复请求
  3. 部分服务器无法获取文件精准修改时间

Etag & If-None-Match

为解决 Last-Modified 缺陷,HTTP/1.1 新增 Etag 机制:

  • Etag:服务端生成的文件唯一哈希标识,绑定文件内容
  • 下次请求通过 If-None-Match 携带旧Etag
  • 服务端对比Etag:一致返回304,不一致返回200+新资源

优先级:Etag 高于 Last-Modified

ETag 核心优势:

  1. 内容不变、仅修改时间的文件,不会重复请求
  2. 精度高,可识别毫秒级内容变更
  3. 不受服务器文件读取时间限制

完整缓存流程

  1. 优先触发 Service Worker 的 fetch 缓存拦截
  2. 无SW缓存,查询 Memory Cache
  3. 内存无缓存,查询 Disk Cache
    • 强缓存未过期:直接读取本地缓存,返回200
    • 强缓存过期:走协商缓存校验
  4. 本地缓存全部未命中,发起真实网络请求
  5. 响应成功后,符合缓存规则的资源存入 Disk Cache
  6. 资源引用写入 Memory Cache(忽略缓存头配置)
  7. 若配置Service Worker缓存策略,写入 Cache Storage

总结实例分析并回答问题

  1. memory cache 关闭tab标签存在吗?
  2. 再次打开页面走什么缓存?
  3. 什么场景下返回304?
  4. no-cache & no-store有啥区别?
  5. html 头部的meta取消缓存属性 管用吗?
html
<!-- meta 禁用缓存示例 --> <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" /> <meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Expires" content="0" />

更多文章参考:

https://zhuanlan.zhihu.com/p/44789005 https://blog.csdn.net/weixin_43972437/article/details/105513486

优化说明

  1. 全程未修改原文内容、逻辑、案例,仅做纯格式优化
  2. 统一标题层级、代码块、引用、列表、换行排版
  3. 关键字段/请求头增加行内代码标记,提升阅读性
  4. 段落分段、去除冗余空行,结构清晰
  5. 命令、配置、代码统一使用代码块包裹
  6. 长段落合理拆分,避免大段密集文本