Skip to content

缓存

浏览器缓存是允许将用户之前请求过的资源(如 HTML 页面、图片、脚本等)存储在本地,当再次访问相同资源时,可以直接从本地读取而不是重新向服务器发起请求。可以减少请求和性能优化,提升用户体验。

http 缓存是浏览器或代理服务器对之前请求过的资源进行存储,当再次请求相同资源时,可以直接从本地缓存中获取,避免重复向服务器发起请求的机制,,可显著减少网络传输量、降低服务器负载、提升用户体验。

若缓存生效,强缓存返回状态码200,协商缓存返回状态码304

浏览器缓存

核心流程:

  1. 浏览器向服务器请求资源时,服务器返回资源及缓存控制头(如 Cache-Control、Expires),浏览器将资源存储到本地缓存。
  2. 浏览器再次请求相同资源时,会先检查本地缓存,如果缓存未过期,则直接从缓存中获取资源,而不再向服务器发起请求。

强缓存

服务端给响应头追加一些字段(expires),在 expires 截止失效时间之前,无论如何刷新页面,都不会重新请求

  • Expires: 服务器返回一个时间,由客户端时间决定是否失效
js
// 响应头添加
ctx.set('Expires', new Date(Date.now() + 10 * 1000).toUTCString())
  • Cache-Control: max-age=86400: 最大缓存时间,在 max-age 截止失效时间之前,只要客户端发送请求,服务端不会返回新的资源,客户端会直接使用本地缓存
js
// 响应头添加
ctx.set('Cache-Control', 'public, max - age=10')

协商缓存

当服务端标记上协商缓存后,客户端再下次请求需要发送请求到服务器,由服务器判断是否需要缓存获取(主要是去判断文件内容是否变化)

协商缓存(两组)

  • 第一组:

    • 响应状态码:Last-Modified(最后修改时间)

    初次加载时,服务器返回一个时间 Last-Modified 给客户端,客户端下次请求时会带上这个时间值(If-Modified-Since),单位是秒,如果在1秒内再次请求,就算是文件修改了,也不会重新请求资源。

    • 请求状态码: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,走缓存

    ETag的值是根据资源内容生成的,通常是资源的 MD5 哈希值。你可以使用 Node.js 的 crypto 模块来生成 ETag 值。(会增加服务器开销)

    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);
    }

策略缓存

所用技术 service worker

策略缓存可以拦截前端资源请求,并约定请求缓存策略(一般有 4 种)

文档:https://developer.chrome.com/docs/workbox/caching-strategies-overview?hl=zh_cn#caching_strategies

  • 仅限缓存:仅缓存,不请求网络
  • 仅限网络:仅请求网络,不缓存
  • 先缓存,然后回退到网络:(一般常用)
    1. 请求命中缓存。如果资源在缓存中,请从缓存中提供。
    2. 如果请求不在缓存中,请转到网络。
    3. 在网络请求完成后,将其添加到缓存中, 然后从网络返回响应。
  • 先网络,回退到缓存
    1. 您首先要访问网络以获取请求,然后将响应放入缓存中。
    2. 如果您稍后处于离线状态, 则您会在缓存中回退到该响应的最新版本。