深入理解前端缓存
前端缓存是所有前端程序员在成长历程中必须要面临的问题,它会让我们的项目得到非常大的优化提升,同样也会带来一些其它方面的困扰。大部分前端程序员也了解一些缓存相关的知识,比如:强缓存、协商缓存、cookie等,但是我相信大部分的前端程序员不了解它们的缓存机制。接下来我将带你们深入理解缓存的机制以及缓存时间的判断公式,如何合理的使用缓存机制来更好的提升优化。我将会把前端缓存分成HTTP缓存和浏览器缓存两个部分来和大家一起聊聊。
HTTP 缓存
HTTP是一种超文本传输协议,它通常运行在TCP之上,从浏览器Network中可以看到,它分为Respnse Headers(响应头)、Request Headers(请求头)两部分组成。

接下来介绍一下与缓存相关的头部字段:

expires
我们先来看一下MDN对于expires的介绍
响应标头包含响应应被视为过期的日期/时间。
备注: 如果响应中有指令为
max-age或s-maxage的Cache-Control标头,则Expires标头会被忽略。
Expires: Wed, 24 Apr 2024 14:27:26 GMT
Cache-Control
Cache-Control是HTTP/1.1中定义的缓存字段,它可以由多种组合使用,分开列如:max-age、s-maxage、public/private、no-cache/no-store等
Cache-Control: max-age=3600, s-maxage=3600, public
max-age是相对当前时间,单位是秒,当设置max-age时则expires就会失效,max-age的优先级更高。
而 s-maxage 与 max-age 不同之处在于,其只适用于公共缓存服务器,比如资源从源服务器发出后又被中间的代理服务器接收并缓存。
public是指该资源可以被任何节点缓存,而private只能提供给客户端缓存。当设置了private之后,s-maxage则会无效
。
强缓存
强缓存的具体流程如下:

上面我们介绍了expires设置的是绝对的时间,它会根据客户端的时间来判断,所以会造成expires不准确,如果我有一个资源缓存到到期时间是2024年4月31日我将客户端时间修改成过期的时间,则在一次访问该资产会重新请求服务器获取最新的数据。
而max-age则是相对的时间,它的值是以秒为单位的时间,但是max-age也会不准确。
那么到底浏览器是怎么判断该资源的缓存是否有效的呢?这里就来介绍一下资源新鲜度的公式。
我们来用生活中的食品新鲜度来举例:
食品是否新鲜 = (生产日期 + 保质期) > 当前日期
那么缓存是否新鲜也可以借助这个公式来判断
缓存是否新鲜 = (创建时间 + expire || max-age) > 缓存使用期
这里的创建时间可以理解为服务器返回资源的时间,它和expires一样是一个绝对时间。
缓存使用期 = 响应使用期 + 传输延迟时间 + 停留缓存时间
响应使用期
响应使用期有两种获取方式:
- max(0, responseTime - dateTime)
- age
responseTime: 是指客户端收到响应的时间
dateTime: 是指服务器创建资源的时间
age:是响应头部的字段,通常是秒为单位
传输延迟时间
传输延迟的时间 = 客户端收到响应的时间 - 请求时间
停留时间
停留时间 = 当前客户端时间 - 客户端收到响应的时间
所以max-age也会失效的问题就是它也使用到了客户端的时间。
协商缓存
协商缓存的具体流程如下:

从上文可以知道,协商缓存就是通过Etag和Last-Modified这两个字段来判断。那么这个Etag的标识是如何生成的呢?
我们可以看node中etag第三方库。
该库会通过isState方法来判断文件的类型,如果是文件形式的话就会使用第一种方法:通过文件内容和修改时间来生成Etag。

第二种方法:通过文件内容和hash值和内容长度来生成Etag。

浏览器缓存
我们访问掘金的网站,查看Network可以看到有Size列有些没有大小的,而是disk cache、memory cache这样的标识。

memory cache翻译就是内存缓存,顾名思义,它是存储在内存中的,优点就是速度非常快,可以看到Time列是0ms,缺点就是当网页关闭则缓存也就清空了,而且内存大小是非常有限的,如果要存储大量的资源的话还是使用磁盘缓存。
disk cache翻译就是磁盘缓存,它是存储在计算机磁盘中的一种缓存,它的优缺点和memory cache相反,它的读取是需要时间的,可以看到上方的图片Time列用了1ms的时间。
缓存获取顺序
- 浏览器会先查找内存缓存,如果存在则直接获取内存缓存中的资源
- 内存缓存没有,就回去磁盘缓存中查找,如果存在就返回磁盘缓存中的资源
- 磁盘缓存没有,那么就会进行网络请求,获取最新的资源然后存入到内存缓存或磁盘缓存
缓存存储优先级
浏览器是如何判断该资源要存储在内存缓存还是磁盘缓存的呢?
打开掘金网站可以看到,发现除了base64图片会从内存中获取,其它大部分资源会从磁盘中获取。

js文件是一个需要注意的地方,可以看到下面的有些js文件会被磁盘缓存有些则会被内存缓存,这是为什么呢?

Initiator列表示资源加载的位置,我们点击从内存获取资源的该列发现资源在HTML渲染阶段就被加载了,列入一下代码
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><title>DocumentÏ</title><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script></head><body><div id="root">这样加载的js资源大概率会存储到内存中</div></body>
</html>
而被内存抛弃的可以发现就是异步资源,这些资源不会被缓存到内存中。
上图我们可以看到有一个Initiator列的值是(index):50但是它还是被内存缓存了,我们可以点击进去看到他的代码如下:

这个js文件还是通过动态创建script标签来动态引入的。
Preload 与 Prefetch
Preload和Prefetch也会影响浏览器缓存的资源加载。
Preload称为预加载,用在link标签中,是指哪些资源需要页面加载完成后立刻需要的,浏览器会在渲染机制介入前加载这些资源。
<link rel="preload" href="//lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/0358ea0.js" as="script">
当使用preload预加载资源时,这些资源一直会从磁盘缓存中读取。
prefetch表示预提取,告诉浏览器下一个页面可能会用到该资源,浏览器会利用空闲时间进行下载并存储到缓存中。
<link rel="prefretch" href="//lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/0358ea0.js"Ï>
使用 prefetch 加载的资源,刷新页面时大概率会从磁盘缓存中读取,如果跳转到使用它的页面,则直接会从磁盘中加载该资源。
使用no-store表示不进行资源缓存。使用no-cache表示告知(代理)服务器不直接使用缓存,要求向源服务器发起请求,而当在响应首部中被返回时,表示客户端可以缓存资源,但每次使用缓存资源前都必须先向服务器确认其有效性,这对每次访问都需要确认身份的应用来说很有用。
当然,我们也可以在代码里加入 meta 标签的方式来修改资源的请求首部:
<meta http-equiv="Cache-Control" content="no-cache" />
示例
这里我起了一个nestjs的服务,该getdata接口缓存10s的时间,Ï代码如下:
@Get('/getdata')getData(@Response() res: Res) {return res.set({ 'Expires': new Date(Date.now() + 10).toUTCString() }).json({list: new Array(1000000).fill(1).map((item, index) => ({ index, item: 'index' + index }))});Ï}
第一次请求,花费了334ms的时间。

第二次请求花费了163ms的时间,走的是磁盘缓存,快了近50%的速度

接下来我们来验证使用Cache-Control是否可以覆盖Exprie,我们将getdata接口修改如下,Cache-Control设置了1s。Ï我们刷新页面可以看到getdata接口并没有缓存,每次都会想服务器发送请求。
@Get('/getdata')getData(@Response() res: Res) {return res.set({ 'Expires': new Date(Date.now() + 10).toUTCString(), 'Cache-Control': 1 }).json({list: new Array(1000000).fill(1).map((item, index) => ({ index, item: 'index' + index }))});}
仔细的同学应该会发现一个问题,清除缓存后的第一次请求和第二次请求Size的大小不一样,这是为什么呢?
打开f12右键刷新按钮,点击清空缓存并硬性重新加载。

我们开启Big request rows更方便查看Size的大小,开启时Size显示两行,第一行就是请求内容的大小,第二行则是实际的大小。

刷新一下,可以看到Size变成了283B大小了。

带着这个问题我们来深入研究一下浏览器的压缩。HTTP2和HTTP3的压缩算法是大致相同,我们就拿HTTP2的压缩算法(HPACK)来了解一下。
HTTP2 HPACK压缩算法
HPACK压缩算法大致分为:静态Huffman(哈夫曼)压缩和动态Huffman哈夫曼压缩,所谓静态压缩是指根据HTTP提供的静态字典表来查找对应的请求头字段从而存储对应的index值,可以极大的减少内催空间。
动态压缩它是在同一个会话级的,第一个请求的响应里包含了一个比如 {list: [1, 2, 3]},那么就会把它存进表里面,后续的其它请求的响应,就可以只返回这个 header 在动态表里的索引,实现压缩的目的
需要详细了解哈夫曼算法原理的可以去这个博客看一看。
Last-Modified 与 If-Modified-Since
Last-Modified代表资源的最后修改时间,其属于响应首部字段。当浏览器第一次接收到服务器返回资源的 Last-Modified 值后,其会把这个值存储起来,并下次访问该资源时通过携带If-Modified-Since请求首部发送给服务器验证该资源是否过期。
yaml
复制代码
Last-Modified: Fri , 14 May 2021 17:23:13 GMT
If-Modified-Since: Fri , 14 May 2021 17:23:13 GMT
如果在If-Modified-Since字段指定的时间之后资源都没有发生更新,那么服务器会返回状态码 304 Not Modified 的响应。
Etag 与 If-None-Match
Etag代码该资源的唯一标识,它会根据资源的变化而变化着,同样浏览器第一次收到服务器返回的Etag值后,会把它存储起来,并下次访问该资源通过携带If-None-Match请求首部发送给服务器验证资源是否过期
Etag: "29322-09SpAhH3nXWd8KIVqB10hSSz66"
If-None-Match: "29322-09SpAhH3nXWd8KIVqB10hSSz66"
如果两者不相同则代表服务器资源已经更新,服务器会返回该资源最新的Etag值。
相关文章:
深入理解前端缓存
前端缓存是所有前端程序员在成长历程中必须要面临的问题,它会让我们的项目得到非常大的优化提升,同样也会带来一些其它方面的困扰。大部分前端程序员也了解一些缓存相关的知识,比如:强缓存、协商缓存、cookie等,但是我…...
K-means聚类算法详解与实战
一、引言 K-means聚类算法是一种无监督学习算法,旨在将数据点划分为K个不同的聚类或群组,使得同一聚类内的数据点尽可能相似,而不同聚类间的数据点尽可能不同。在图像处理、数据挖掘、客户细分等领域有着广泛的应用。本文将通过图文结合的方…...
python数据分析-糖尿病数据集数据分析预测
一、研究背景和意义 糖尿病是美国最普遍的慢性病之一,每年影响数百万美国人,并对经济造成重大的经济负担。糖尿病是一种严重的慢性疾病,其中个体失去有效调节血液中葡萄糖水平的能力,并可能导致生活质量和预期寿命下降。。。。 …...
【前端】 nvm安装管理多版本node、 npm install失败解决方式
【问题】If you believe this might be a permissions issue, please double-check the npm ERR! permissio或者Error: EPERM: operation not permitted, VScode中npm install或cnpm install报错 简单总结,我们运行npm install 无法安装吧包,提示权限问题…...
第11天:API开发与REST framework
第11天:API开发与REST framework 目标 使用Django REST framework构建RESTful API。 任务概览 学习序列化器的概念和使用方法。创建API视图和路由。实现API的权限和认证。 详细步骤 1. 学习序列化器 序列化器是Django REST framework中用于数据转换的组件&am…...
2024 年解锁 Android 手机的 7 种简便方法
您是否忘记了 Android 手机的 Android 锁屏密码,并且您的手机已被锁定?您需要使用锁屏解锁 Android 手机?别担心,您不是唯一一个忘记密码的人。我将向您展示如何解锁 Android 手机的锁屏。 密码 PIN 可保护您的 Android 手机和 G…...
利用机器学习重构视频中的人脸
引言 中国与英国的研究团队携手合作,开创了一种创新的视频面孔重塑技术。这项技术能够以极高的一致性对视频中的面部结构进行逼真的放大和缩小,且避免了常见伪影的产生。 从研究人员选取的YouTube视频样例中可见,经过处理后,女演…...
2021数学建模C题目– 生产企业原材料的订购与运输
C 题——生产企业原材料的订购与运输 思路:该题主要是通过对供应商的供货能力和运送商的运货能力进行估计,给出合适的材料订购方案 程序获取 第一题问题思路与结果: 对 402 家供应商的供货特征进行量化分析,建立反映保障企业生…...
C# OpenCvSharp 图像复制-clone、copyTo
在C#中使用OpenCvSharp库处理图像时,clone和copyTo是两个非常常用的函数。理解和合理使用这些函数可以帮助你在图像处理项目中更高效地操作图像数据。本文将详细介绍这两个函数的使用方法,并通过具体的示例来说明它们的实际应用。 1. clone 函数 定义 …...
中国投入到终止遗传性疾病的战斗
中国投入到终止遗传性疾病的战斗 编译 李升伟 于2006年6月在澳大利亚的墨尔本会议上启动的人类变异组计划(Human Variome Project,简称HVP),旨在全球范围内广泛收集所有基因和蛋白质序列变异和多态性的数据,采用全基…...
PCL common中常见的基础功能函数
文章目录 一、common模块中的头文件二、common模块中的基本函数1、angles.h2、centroid.h1)计算给定一群点的3D中心点,并且返回一个三维向量2)计算给定的三维点云的协方差矩阵。3)计算正则化的3*3的协方差矩阵以及给定点云数据的中心点4)利用一组点的指数对其进行一般的、…...
力扣每日一题 6/22 字符串/贪心
博客主页:誓则盟约系列专栏:IT竞赛 专栏关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞👍收藏⭐评论✍ 2663.字典序最小的美丽字符串【困难】 题目: 如果一个字符串满…...
MCT Self-Refine:创新集成蒙特卡洛树搜索 (MCTS)提高复杂数学推理任务的性能,超GPT4,使用 LLaMa-3 8B 进行自我优化
📜 文献卡 题目: Accessing GPT-4 level Mathematical Olympiad Solutions via Monte Carlo Tree Self-refine with LLaMa-3 8B作者: Di Zhang; Xiaoshui Huang; Dongzhan Zhou; Yuqiang Li; Wanli OuyangDOI: 10.48550/arXiv.2406.07394摘要: This pape…...
自制HTML5游戏《开心消消乐》
1. 引言 游戏介绍 《开心消消乐》是一款基于HTML5技术开发的网页游戏,以其简单的操作方式、轻松的游戏体验和高度的互动性,迅速在社交平台上获得了广泛的关注和传播。玩家通过消除相同类型的元素来获得分数,游戏设计巧妙,易于上手…...
【C++】平衡二叉树(AVL树)的实现
目录 一、AVL树的概念二、AVL树的实现1、AVL树的定义2. 平衡二叉树的插入2.1 按照二叉排序树的方式插入并更新平衡因子2.2 AVL树的旋转2.2.1 新节点插入较高左子树的左侧(LL平衡旋转)2.2.2 新节点插入较高右子树的右侧(RR平衡旋转)…...
第一百一十八节 Java面向对象设计 - Java接口
Java面向对象设计 - Java接口 什么是接口? Java中的接口定义了一个引用类型来创建抽象概念。接口由类实现以提供概念的实现。 在Java 8之前,一个接口只能包含抽象方法。 Java 8允许接口具有实现的静态和默认方法。 接口通过抽象概念定义不相关类之间…...
Flink nc -l -p 监听端口测试
1、9999端口未占用 netstat -apn|grep 99992、消息发送端 nc -l -k -p 9999 {"user":"ming","url":"www.baidu1.com", "timestamp":1200L, "score":1} {"user":"xiaohu","url":…...
在IntelliJ IDEA中使用Spring Boot:快速配置
使用IntelliJ IDEA开发Spring Boot应用程序可以极大地提高开发效率,因为IDEA提供了许多便捷的功能,比如自动补全、代码分析、热部署等。以下是一篇可能的CSDN博客文章草稿,介绍如何在IntelliJ IDEA中使用Spring Boot: 在IntelliJ …...
django filter 批量修改
django filter 批量修改 在Django中,如果你想要批量修改记录,可以使用update()方法。这个方法允许你在一个查询集上执行批量更新,而不需要为每条记录生成单独的数据库事务。 以下是一个使用update()方法批量修改记录的例子: fro…...
maven:中央仓库验证方式改变:401 Content access is protected by token
前几天向maven中央仓库发布版本,执行上传命令mvn release:perform时报错了: [ERROR] Failed to execute goal org.sonatype.plugins:nexus-staging-maven-plugin:1.6.13:deploy (injected-nexus-deploy) on project xxxxx: Failed to deploy artifacts: …...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
