Appearance
缓存
缓存的目的:减少网络请求,减轻服务器负担,提高页面加载速度
强缓存和协商缓存的主要区别:是否会向服务器发送请求
- 强缓存:不会向服务器发送请求
- 协商缓存:会向服务器发送请求,通过一些字段与服务器进行验证,来判断是否需要更新缓存。
若缓存生效,强缓存返回状态码200
,协商缓存返回状态码304
。他们缓存的内容:静态资源(HTML、CSS、JS、图片、字体)
强缓存
Expires
Expires
: 服务器返回一个时间戳(资源的过期时间),由客户端时间决定是否失效
服务端给响应头追加一些字段(expires),在 expires 截止失效时间之前,无论如何刷新页面,都不会重新请求
js
// 响应头添加
ctx.set('Expires', new Date(Date.now() + 10 * 1000).toUTCString())
Cache-Control
Cache-Control
:资源缓存的有效时间,在有效时间内,只要客户端发送请求,服务端不会返回新的资源,客户端会直接使用本地缓存。
取值:
no-store
:禁止缓存。表示不应存储请求或响应的任何部分。no-cache
:需要重新验证缓存。客户端需要向服务器发送一个请求来确认缓存的有效性。max-age=<seconds>
:指定资源在缓存中的最大存储时间,单位为秒。s-maxage=<seconds>
:类似于max-age
,但仅适用于代理服务器缓存,而不适用于浏览器缓存。public
:表示响应可以被任何缓存(包括代理服务器)缓存,即响应是公共资源。private
:表示响应只能被浏览器缓存,不允许代理服务器缓存。适用于包含用户特定信息的响应。must-revalidate
:表示客户端必须在使用已缓存的响应之前重新验证该响应的有效性。proxy-revalidate
:类似于must-revalidate
,但仅适用于代理服务器缓存。max-stale[=seconds]
:表示客户端愿意接受已过期的响应,可指定最长过期时间(可选)。min-fresh=seconds
:表示客户端希望获取一个在指定时间内不会过期的响应。immutable
:指示响应不会随时间的推移而发生更改,适用于长期缓存的不变资源。no-transform
:禁止代理服务器对响应进行任何形式的转换,例如,不要压缩或修改内容。only-if-cached
:表示客户端只接受缓存的响应,不要向服务器发送请求。
js
// 响应头添加
ctx.set('Cache-Control', 'public, max-age=10')
js
app.use(
express.static('public', {
setHeaders: (res, path) => {
// 设置强缓存,这里设置缓存时间为 1 小时(3600 秒)
res.setHeader('Cache-Control', 'public, max-age=3600')
}
})
)
协商缓存
浏览器通过一些字段与服务器进行验证,来判断是否需要更新缓存。如果服务器有更新的资源,就会返回状态码304
,然后浏览器会向服务器拿最新的资源,去替换之前的本地缓存。
协商缓存(两组)
第一组:
- 响应状态码:
Last-Modified(时间戳,资源最后修改时间)
初次加载时,服务器返回一个时间
- 请求状态码:
If-Modified-Since
第一次响应后,客户端发送请求时,会在
If-Modified-Since
携带Last-Modified
的值,后端去判断是否改变,变了就返回最新数据,没变就返回状态码304
,走缓存js// 服务器 async lastModifiedTest(ctx) { // 模拟资源数据 const resource = { id: 1, name: 'Example Resource' }; // 模拟最后修改时间 const lastModifiedTime = 1689253117727; // 检查客户端发送的 if-modified-since const ifModifiedSince = ctx.get('If-Modified-Since'); if (ifModifiedSince && new Date(ifModifiedSince).getTime() > lastModifiedTime) { // 如果请求的 If-Modified-Since 大于最后修改时间,则返回缓存状态码 ctx.status = 304; return; } // 模拟3秒请求 await new Promise(resolve => { setTimeout(() => { resolve(); }, 3000); }); // 设置响应头中的 ETag ctx.set('Last-Modified', new Date(lastModifiedTime).toUTCString()); this.success(ctx, resource); }
- 响应状态码:
第二组:(升级)
- 响应状态码:
Etag(文件hash,资源的唯一资源)
文件 hash 值赋给
Etag
,服务端响应的是Etag
- 请求状态码:
If-None-Match
去判断
Etag
是否改变,没变就返回状态码304
,走缓存js// 服务器 async etagTest(ctx) { // 模拟资源数据 const resource = { id: 1, name: 'Example Resource' }; // 生成 ETag const resourceETag = md5(`${resource.id}${resource.name}`); // 检查客户端发送的 If-None-Match 头 const requestETag = ctx.get('If-None-Match'); if (requestETag === resourceETag) { // 如果请求的 ETag 与资源的 ETag 匹配,则返回缓存状态码 ctx.status = 304; return; } // 模拟3秒请求数据 await new Promise(resolve => { setTimeout(() => { resolve(); }, 3000); }); // 设置响应头中的 ETag ctx.set('ETag', resourceETag); this.success(ctx, resource); }
- 响应状态码:
启发式缓存
启发式缓存是浏览器默认的缓存方式,当浏览器没有从服务器获取到明确的缓存指令(如Cache-Control
、Expires
等头部信息)时,它会基于资源的类型、大小、上次访问时间等因素,运用内置的启发式算法来决定将资源在本地缓存多长时间。
启发式缓存时间公式:缓存时间 = (当前时间 - Last-Modified) * 10%
Last-Modified
:服务器中资源最后被修改的日期
策略缓存
所用技术 service worker
,是一个 web worker
,它运行在浏览器后台的脚本,与页面分离,不会影响页面的加载速度。
service worker
功能:
- 拦截请求,缓存资源
- 离线功能(PWA),离线本质就是不与服务器发生请求,直接从缓存中获取数据,service worker 可以拦截请求,并优先从缓存中获取数据
策略缓存可以拦截前端资源请求,并约定请求缓存策略
仅限缓存
:仅缓存,不请求网络仅限网络
:仅请求网络,不缓存先缓存,然后回退到网络
:(一般常用)- 请求命中缓存。如果资源在缓存中,请从缓存中提供。
- 如果请求不在缓存中,请转到网络。
- 在网络请求完成后,将其添加到缓存中, 然后从网络返回响应。
先网络,回退到缓存
:- 您首先要访问网络以获取请求,然后将响应放入缓存中。
- 如果您稍后处于离线状态, 则您会在缓存中回退到该响应的最新版本。
面试题 1:如果要缓存接口数据,怎样缓存?
- http 缓存头:
Cache-Control
、Expires
、Last-Modified
、ETag
,去缓存接口响应的数据- 请求缓存头:
If-Modified-Since
,If-None-Match
- 响应端缓存头:
Last-Modified
,ETag
- 请求缓存头:
- localStorage、sessionStorage 小规模数据
- indexedDB 较大规模数据
- service worker 进行请求的拦截,缓存数据
面试题 2:如果使用 localStorage 缓存数据,可能会有安全问题,会怎么做?
localStorage 存储的数据是明文,不安全
解决方法:
- 敏感数据加密后存储
- 避免敏感数据存储到 localStorage 中
HttpOnly
和Secure
标记 cookie 替换(标记 cookie 替换通常是指在网络环境中,对与特定标记相关的 cookie 进行更新、修改或替换的操作。)