浏览器存储与缓存入门指南:前端性能优化的第一块基石

364 阅读7分钟

一、浏览器存储

浏览器存储是网页在用户本地保存数据的技术,主要用于实现数据持久化、减少服务器请求和离线访问能力。

1. localStorage(本地存储)

  • 存储特性:提供永久性存储,容量一般在5-10MB之间,以字符串形式存储键值对。
  • 生命周期:页面关闭后依然保留

2. sessionStorage(会话存储)

  • 存储特性:与localStorage类似,同样存储字符串类型的键值对,容量也在5-10MB左右。
  • 生命周期:其数据的有效期与页面会话相同,当页面会话结束(如浏览器关闭)时,数据将被清除。

3. cookies存储

  • 存储特性:存储带过期时间的键值对。由后端通过设置Max-Age来控制有效存储时长,存储在cookies中的内容会在浏览器发送请求时自动携带在请求头中。
  • 跨域能力:cookies具有跨域访问的能力,这使得它在不同域名间的数据交互中发挥作用。

4.indexedDB(客户端数据库)

  • 存储特性:是一种在浏览器端的数据库,理论上存储大小无上限,能够存储表结构以及复杂的数据类型。
  • 应用场景:适用于需要大量数据存储和复杂数据操作的Web应用,如离线应用、大型数据缓存等。

本地存储、会话存储、客户端数据库都不能跨域,cookies存储可以跨域。

特性localStoragesessionStoragecookiesindexedDB
数据类型字符串字符串字符串/数值复杂结构
存储容量5-10MB5-10MB≤4KB理论上无限
生命周期永久会话级可设置过期永久
自动发送请求
同源策略严格限制严格限制支持跨子域严格限制
数据操作方式同步API同步API同步API异步API
事务支持不支持不支持不支持完整支持
主要应用场景静态数据存储临时数据暂存会话管理大型结构化数据

二、浏览器缓存(也叫HTTP缓存)

什么是浏览器缓存?

浏览器缓存是浏览器将已请求资源(如HTML、CSS、图片)暂存本地的机制。通过复用本地副本,减少重复请求,显著提升页面加载效率并降低网络消耗。

将页面上长时间不更新的资源缓存到浏览器上,下次访问页面时,该部分资源直接从缓存中获取,从而减少了网络请求的次数,提高了页面的加载速度。

浏览器缓存数据实际存储在本地磁盘的缓存目录中(不同浏览器路径不同)。当用户使用强制刷新时,浏览器会跳过缓存校验机制。

1. 强缓存

  • 设置方式:通过在响应头中设置Cache-Control字段,其值为max-age=xxx(单位为秒),来指定缓存的有效期。
  • 工作原理:当浏览器请求资源时,如果该资源仍在强缓存有效期内,则直接从浏览器的缓存中获取,无需向后端发送请求。
  • 局限性:对于通过浏览器url地址栏直接请求的资源,请求头中会自动携带Cache-Control: max-age=0导致强缓存失效
const { ext } = path.parse(filePath); //获取文件后缀
res.writeHead(200, {
        'content-type': mime.getType(ext), // 根据文件后缀生成对应的content-type
        'cache-control': 'max-age=86400', // 强缓存 1 天
      })

在强缓存有效期内,浏览器直接使用本地缓存,不会产生任何网络请求,强缓存只有在失效的那一刻才会重新发起新的请求。

只用强缓存可以把除了url地址栏访问的资源存起来,但是后端资源更新了就无法第一时间让前端获取到, 所以还需要协商缓存

2. 协商缓存

为了解决强缓存的局限性,引入了协商缓存机制。协商缓存通过在浏览器和服务器之间交换特定的头部信息,来判断资源是否需要重新获取。

(1) Last-Modified 与 If-Modified-Since

浏览器第一次访问资源时,响应头中携带last-modified字段,该字段的值为资源的最后修改时间。当浏览器接收到响应头后,会在该资源再次被请求时,在请求头中自动携带 If-Modifed-Since字段

  • Last-Modified:服务器在响应头中携带该字段,值为资源的最后修改时间。浏览器在首次访问资源时会收到这个时间戳。
  • If-Modified-Since:当浏览器再次请求同一资源时,会在请求头中自动携带该字段,其值为上次收到的 Last-Modified 值。服务器收到请求后,会比较 If-Modified-Since 的值和资源当前的最后修改时间。如果两者一致,说明资源未被修改,服务器返回 304 状态码,浏览器从缓存中获取资源;如果不一致,则返回 200 状态码,浏览器就会重新获取最新资源。
 const timeStamp = req.headers['if-modified-since'] //从请求头获取if-modified-since

      let status = 200
      if (timeStamp && Number(timeStamp) === stats.mtimeMs) { // 文件没有发生过更改
        status = 304
      }

      res.writeHead(status, {
        'content-type': mime.getType(ext),
        'cache-control': 'max-age=86400', // 强缓存 1 天
        'last-modified': stats.mtimeMs, // 最后修改时间
      })

      if (status === 200) {
        const readStream = fs.createReadStream(filePath); // 创建可读流
        readStream.pipe(res); // 将可读流的数据,通过管道,输出到前端
      } else {
        return res.end();
      }

(2)ETag 与 If-None-Match

  • ETag:也叫文件指纹。服务器根据资源的内容生成一个唯一的标识(如 MD5 哈希值),并在响应头中携带该字段。浏览器首次访问时会收到这个标识。
  • If-None-Match:浏览器再次请求时,在请求头中自动携带该字段,其值为上次收到的 ETag 值。服务器比较 If-None-Match 的值和当前资源的 ETag 值。如果一致,返回 304 状态码,浏览器从缓存中获取资源;如果不一致,返回 200 状态码和更新后的资源。
const ifNoneMatch = req.headers['if-none-match'] // 从请求头获取if-none-match

      checksum.file(filePath, (err, sum) => {//MD5加密方式
        sum = `"${sum}"`

        if (ifNoneMatch === sum) {  // 文件没有变化

          res.writeHead(304, {
            'Content-Type': mime.getType(ext),
            'etag': sum,
          })
          res.end()

        } else {

          res.writeHead(200, {
            'Content-Type': mime.getType(ext),
            'Cache-Control': 'max-age=1000000',
            'etag': sum,
          })
          const resStream =  fs.createReadStream(filePath)
          resStream.pipe(res)

        }

以上代码中,引入checksum,计算文件的MD5值,同一份文件加密得到的MD5值是一样的,文件内容不一样,MD5值就不一样。把加密后得到的MD5值作为文件的标识Etag,并设置在响应头里面。

以上2种手段,用法基本一致,Last-Modified是以最后一次修改时间为标识,ETag是以文件内容作为标识。 MD5加密是要耗费性能的。大文件加密耗时比较久,所以一般采用last-modified + If-Modifed-Since 文件不是特别大且不易修改,采用文件指纹etag + If-None-Match

方案优点缺点
Last-Modified计算成本低内容不变但文件被修改也会重新发请求
ETag内容级精确验证大文件计算消耗资源

缓存优先级

强缓存优先:浏览器始终优先检查本地强缓存,若在有效期内(Cache-Control/Expires未过期),直接使用缓存且不发送任何请求

失效触发协商:仅当强缓存过期时,才携带验证头(If-Modified-Since/If-None-Match)发起协商请求

后端资源更新了,如何让前端第一时间拿到呢?

在资源文件名后添加哈希值(如:app.3a7b6c8d.js),当资源更新时,哈希值也会改变。这样,浏览器会将其视为不同的资源,从而重新请求和缓存。确保资源更新后URL变化,自动绕过旧缓存。

总结

HTTP 缓存机制是前端性能优化的重要组成部分。强缓存和协商缓存不是对立的,强缓存和协商缓存相辅相成,共同构成了高效的缓存策略。合理运用这些机制,可以显著提升网页的加载速度和用户体验,同时减轻服务器的负载压力。