我正在参与掘金创作者训练营第5期,点击了解活动详情
前言
提到缓存,想必各位都能想到它的作用 —— 是性能优化的一个方面,用来节约时间,提升效率,在前端有很多场景都有用到缓存:
webpack V5的cache配置选项(Cache the generated webpack modules and chunks to improve build speed,通过缓存生成的webpack modules,chunks来提升构建速度)webpack打包项目时,通过在一些性能开销较大的loader之前添加cache-loader来提升构建的速度。babel-loader也有提供cacheDirectory的配置,目的也是为了提升构建速度。- 我们平时在写代码时,也可能会对一些不会经常变动的数据进行缓存,第一次加载后就存到一个变量,下次再加载先判断缓存中有没有,有就从缓存返回,没有才发送http请求获取。
- 甚至在项目中对
axios进行封装时,会对请求进行JS层面的缓存,简单来说就是根据请求接口,请求类型,请求参数生成一个随机字符串当作缓存的key,第一次请求成功后将response存到一个store并设置有效期,之后的请求,只要缓存的key存在store中并且没有失效就直接返回store中的数据。合理设置有效期,设置缓存的大小效果还是很棒的,算是一种牺牲空间换时间的做法。 - 当然
HTTP也有自己的缓存策略,接下来主要聊聊HTTP缓存
HTTP缓存
概念
HTTP缓存存储请求的响应,并将存储的响应重用于后续请求。
能缓存的请求
常见的 HTTP 缓存只能存储 GET 响应,对于其他类型的响应则无能为力。
缓存的类型
按照缓存可存放的位置可分为 私有的缓存 和 共享的缓存
私有的缓存
私有缓存是绑定到特定客户端的缓存,通常是浏览器缓存,存储的响应只在自己客户端,可以存储该用户的个性化响应,通过在响应头设置Cache-Control: private指定。
共享的缓存
位于客户端和源服务器之间的缓存,比如代理服务器,反向代理服务器等
如何控制缓存
通过服务端在响应头添加一些标识来控制缓存:
Cache-control
Cache-control有以下几个选项:
no-store不允许使用缓存no-cache可以缓存,但是不论缓存是否过期,要先与服务器进行协商才可以使用private表示该响应是专用于某单个用户的,中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中public表示该响应可以被任何中间人(比如中间代理、CDN 等)缓存max-age=<seconds>表示资源能够被缓存的最大时间must-revalidate必须先验证缓存是否过期,已过期的缓存要先与服务器进行协商
Etag
缓存的一种强校验器,ETag 响应头是一个对浏览器来说无需理解,只需要按规定使用即可的值。浏览器不知道 ETag 代表什么,不能预测它的值是多少。如果资源请求的响应头里含有 ETag,客户端可以在后续的请求的头中带上 If-None-Match 头来验证缓存
Last-Modified
使用显式时间指定资源上一次修改日期,作为一种弱校验器。说它弱是因为它只能精确到一秒。如果响应头里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
Expires
使用显式时间来指定缓存的生存期
Expires: Tue, 28 Feb 2022 22:22:22 GMT
Pragma
Pragma 效果与Cache-Control: no-cache 相同,通常设置用来兼容HTTP1.0
验证缓存是否可用
按照缓存是否可用还可以将缓存分为强缓存和协商缓存
强缓存
有效的(未过期的)缓存,可直接使用,返回200,from memory cache / from disk cache
举个🌰,先打开掘金网页,再刷新,能看到这个资源就是from memory cache
再看看下面几个,还有
from disk cache的
注意到控制台面板有个Disable cache的选项,不要勾选才能看到效果哦,勾选代表禁用了缓存。
协商缓存
过期的缓存,需要带上相应的请求头跟服务器协商看能否使用,能否使用取决于请求资源是否有更改。
下面看下是怎么验证的。
Last-Modified + If-Modified-Since
通过express模拟一个服务器来测试下:(至于如何搭建一个express服务,在此不过多介绍,请参考Express - 基于 Node.js 平台的 web 应用开发框架 - Express 中文文档 | Express 中文网 (expressjs.com.cn)
// app.js
const express = require('express')
const app = express()
const options = {
etag: false,
setHeaders: function (res, path, stat) {
res.set('Cache-Control', 'max-age=60') // 设置一分钟有效期
}
}
app.use('/static', express.static('public', options))
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p>test</p>
</body>
<script src="./javascripts//index.js" type="application/javascript"></script>
</html>
const a = 123456789
启动服务
首次访问
我们主要看index.js这个文件,Cache-Control: max-age=60代表着在未来的一分钟内,这个资源都是有效的, 也就是说在一分钟内所有这个资源的请求都不会向源服务器请求,而是直接从缓存读取。
在有效期内刷新:
有效期之外刷新
当一分钟过后,缓存就过期了,再次请求这个资源,浏览器会带上If-Modified-Since:Sun, 31 Jul 2022 14:31:09 GMT请求头和源服务器协商资源是否可用。
此时刷新:
返回
304 Not Modified, 表示浏览器可以使用本地缓存文件。304 的响应头同时更新缓存文档的过期时间。(没有更改)
更改服务器文件刷新
如果我们先把index.js的内容改了,再刷新会是怎么样呢?
更改index.js
刷新:
返回
200 ok表示有更改
Etag + If-None-Match
开启Etag
app.js
const express = require('express')
const app = express()
const options = {
etag: true,
setHeaders: function (res, path, stat) {
res.set('Cache-Control', 'max-age=60')
}
}
app.use('/static', express.static('public', options))
访问:
同样有效期一分钟。
在有效期内刷新
过期后刷新
缓存的好处
- 由于不需要将请求传递到源服务器,因此客户端和缓存越近,响应速度就越快。最典型的例子是浏览器本身为浏览器请求存储缓存。
- 当响应可重用时,源服务器不需要处理请求,这减少了服务器上的负载。