包阅导读总结
1. 关键词:前端缓存、HTTP 缓存、浏览器缓存、协商缓存、强缓存
2. 总结:本文深入探讨前端缓存,包括 HTTP 缓存和浏览器缓存,介绍了相关头部字段、压缩算法、协商与强缓存机制、缓存获取顺序及存储优先级等,帮助前端程序员更好地理解和运用缓存优化项目。
3. 主要内容:
– HTTP 缓存
– 相关头部字段
– expires:包含响应过期的日期/时间,若有 `Cache-Control` 的 `max-age` 或 `s-maxage` ,则被忽略。
– Cache-Control:多种组合,如 `max-age` 相对当前时间,优先级高于 `expires` 。
– 压缩算法:HTTP2 的 HPACK 压缩算法,包括静态和动态哈夫曼压缩。
– 协商字段:`Last-Modified` 与 `If-Modified-Since` 、`Etag` 与 `If-None-Match` 。
– 强缓存
– 流程及新鲜度公式。
– 协商缓存
– 流程及 `Etag` 生成方式。
– 浏览器缓存
– 内存缓存:速度快,网页关闭清空。
– 磁盘缓存:读取需时间,优缺点与内存缓存相反。
– 缓存获取顺序:先内存,再磁盘,最后网络请求。
– 缓存存储优先级:如 base64 图片存内存,异步资源可能不存内存。
– `Preload` 与 `Prefetch` 对缓存资源加载的影响。
思维导图:
文章地址:https://mp.weixin.qq.com/s/snlx8JnV7e6LsrgM3Im5hA
文章来源:mp.weixin.qq.com
作者:sorryhc
发布时间:2024/7/7 10:54
语言:中文
总字数:3632字
预计阅读时间:15分钟
评分:88分
标签:前端缓存,HTTP缓存,浏览器缓存,缓存策略,资源优化
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
关注公众号,“技术干货”及时!
前端缓存是所有前端程序员在成长历程中必须要面临的问题,它会让我们的项目得到非常大的优化提升,同样也会带来一些其它方面的困扰。大部分前端程序员也了解一些缓存相关的知识,比如:「强缓存」、「协商缓存」、「cookie」等,但是我相信大部分的前端程序员不了解它们的缓存机制。接下来我将带你们深入理解缓存的机制以及缓存时间的判断公式,如何合理的使用缓存机制来更好的提升优化。我将会把前端缓存分成HTTP缓存和浏览器缓存两个部分来和大家一起聊聊。
HTTP 缓存
HTTP是一种超文本传输协议,它通常运行在TCP之上,从浏览器Network中可以看到,它分为Respnse Headers(响应头)
、Request Headers(请求头)
两部分组成。

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

expires
我们先来看一下MDN[1]对于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则会无效。」
使用no-store
表示不进行资源缓存。使用no-cache
表示告知(代理)服务器不直接使用缓存,要求向源服务器发起请求,而当在响应首部中被返回时,表示客户端可以缓存资源,但每次使用缓存资源前都「必须」先向服务器确认其有效性,这对每次访问都需要确认身份的应用来说很有用。
当然,我们也可以在代码里加入 meta 标签的方式来修改资源的请求首部:
<metahttp-equiv="Cache-Control"content="no-cache"/>
示例
这里我起了一个nestjs
的服务,该getdata
接口缓存10s
的时间,Ï代码如下:
@Get('/getdata')
getData(@Response()res:Res){
returnres.set({'Expires':newDate(Date.now()+10).toUTCString()}).json({
list:newArray(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){
returnres.set({'Expires':newDate(Date.now()+10).toUTCString(),'Cache-Control':1}).json({
list:newArray(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 在动态表里的索引,实现压缩的目的
需要详细了解哈夫曼算法原理的可以去这个博客[2]看一看。
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
值。
强缓存
强缓存的具体流程如下:

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

从上文可以知道,协商缓存就是通过Etag
和Last-Modified
这两个字段来判断。那么这个Etag
的标识是如何生成的呢?
我们可以看node中etag[3]第三方库。
该库会通过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渲染阶段就被加载了,列入一下代码
<!DOCTYPEhtml>
<htmllang="en">
<head>
<metacharset="UTF-8"/>
<title>DocumentÏ</title>
<scriptsrc="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<body>
<divid="root">这样加载的js资源大概率会存储到内存中</div>
</body>
</html>
而被内存抛弃的可以发现就是异步资源,这些资源不会被缓存到内存中。
上图我们可以看到有一个Initiator
列的值是(index):50
但是它还是被内存缓存了,我们可以点击进去看到他的代码如下:

这个js文件还是通过动态创建script
标签来动态引入的。
Preload 与 Prefetch
Preload
和Prefetch
也会影响浏览器缓存的资源加载。
Preload
称为预加载,用在link标签中,是指哪些资源需要页面加载完成后立刻需要的,浏览器会在渲染机制介入前加载这些资源。
<linkrel="preload"href="//lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/0358ea0.js"as="script">
当使用preload
预加载资源时,这些资源一直会从磁盘缓存中读取。
prefetch
表示预提取,告诉浏览器下一个页面可能会用到该资源,浏览器会利用空闲时间进行下载并存储到缓存中。
<linkrel="prefretch"href="//lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/0358ea0.js"Ï>
使用 prefetch 加载的资源,刷新页面时大概率会从磁盘缓存中读取,如果跳转到使用它的页面,则直接会从磁盘中加载该资源。
Reference
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Expires: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FHTTP%2FHeaders%2FExpires
[2]https://blog.csdn.net/qq_38937634/article/details/111410191: https://link.juejin.cn?target=https%3A%2F%2Fblog.csdn.net%2Fqq_38937634%2Farticle%2Fdetails%2F111410191
[3]https://www.npmjs.com/package/etag?activeTab=code: https://link.juejin.cn?target=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fetag%3FactiveTab%3Dcode