告别重复加载:掌握浏览器强缓存与协商缓存策略

浏览器缓存的核心思想

核心目的是减少网络请求的发起次数减少服务器需要传输的数据量,从而显著提升页面加载速度,减轻服务器压力,提升用户体验。

浏览器的缓存策略主要分为两个阶段:强缓存协商缓存

分层解析

强缓存 (Strong Cache)

核心: 不问服务器,直接用自己的副本。
目标: 根本不会发起HTTP请求。

  • 工作原理 :浏览器在请求一个资源时,会先检查本地缓存。如果发现该资源的缓存副本,并且它尚未过期 ,浏览器就会直接使用这个副本,完全不会向服务器发送任何请求 。这个过程发生在浏览器内部,是静默的,因此在开发者工具的Network面板中看到该请求的状态码是 200 (from disk cache)表示在磁盘当中打开多次的资源200 (from memory cache)表示在内存当中,浏览器关闭则释放

  • 如何控制 - HTTP Header

    • Expires (HTTP/1.0):一个绝对的过期时间(GMT格式),例如 Expires: Wed, 21 Oct 2024 07:28:00 GMT。缺点是如果客户端和服务器时间不一致,会导致缓存判断错误。

    • Cache-Control (HTTP/1.1):优先级高于Expires,提供了更灵活、更精确的控制。常用指令:

      • max-age=:设置缓存的最大有效时间,单位是秒(相对时间)。例如 Cache-Control: max-age=3600 表示资源1小时内有效。
      • public:响应可以被任何对象(客户端、代理服务器等)缓存。
      • private:响应只能被单个用户(浏览器)缓存,不能被代理服务器缓存。
      • no-cache不是不缓存 ,而是使用缓存前,必须先向服务器验证(即跳过强缓存,直接进入协商缓存阶段)。
      • no-store真正的不缓存,完全不使用任何缓存策略。每次都要从服务器重新获取。

协商缓存 (Negotiation Cache)

核心: 问问服务器,我这个副本还能不能用。
目标: 可能省去传输响应体的开销。

  • 触发条件 :当强缓存失效(过期)时,浏览器就会启用协商缓存。

  • 工作原理 :浏览器会向服务器发起一个条件性请求,携带一些"验证字段"。服务器根据这些字段判断客户端的缓存副本是否依然有效。

    • 如果有效,服务器返回 304 Not Modified,响应体为空,告诉浏览器:"你的副本没变,继续用吧!"。
    • 如果失效,服务器返回 200 OK 和完整的资源内容。
  • 如何控制 - 成对的HTTP Header

    • 第一对:Last-Modified / If-Modified-Since

      • 服务器响应Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT(资源最后修改时间)。
      • 浏览器请求 :下次请求时,带上 If-Modified-Since: [上次收到的Last-Modified值]
      • 缺点:精度到秒,如果文件在1秒内多次改动,无法识别;有时候文件内容没变,但修改时间变了(比如touch了一下),会导致不必要的重新下载。
    • 第二对:ETag / If-None-Match (优先级更高)

      • 服务器响应ETag: "a5d7f8e3q1w2"(一个唯一标识符,通常是文件的哈希值或版本号)。
      • 浏览器请求 :下次请求时,带上 If-None-Match: [上次收到的ETag值]
      • 优点 :比Last-Modified更精确,能感知文件内容的微小变化。完美解决上述"秒级修改"和"仅修改时间"的问题。

对比和总结

特性 强缓存 协商缓存
是否发请求 不发
目标 完全不请求,极致速度 可能省去响应体(返回304)
状态码 200 (from cache) 304 Not Modified
控制字段 Cache-Control, Expires ETag/If-None-Match, Last-Modified/If-Modified-Since
优先级 先执行 强缓存失效后执行

完整的缓存决策流程可以概括为:

浏览器加载资源时 -> 先看有没有缓存 -> 有缓存,再看Cache-Control/Expires是否过期 -> 未过期 ,强缓存生效 -> 已过期 ,则发起请求带上If-None-Match/If-Modified-Since -> 服务器验证 -> 有效返回304,无效返回200和新资源。

实际应用中

在实际的前端项目中,我们通常会通过Webpack、Vite等构建工具来管理静态资源的缓存:

  1. 哈希文件名 :我们对静态资源(JS, CSS, 图片)的文件名使用内容哈希(如 app.a5d7f8e3.js)。这样一旦文件内容改变,文件名就会变,相当于请求了一个全新的URL,因此可以设置非常长的强缓存时间(比如一年) ,即 Cache-Control: max-age=31536000。这是性能最佳实践。
  2. HTML文件 :HTML是入口文件,通常我们将其设置为 Cache-Control: no-cache 或较短的最大存活时间,确保用户能及时获取到最新的页面和最新的资源链接。

通过这种策略,我们既保证了静态资源的高缓存命中率,又能保证内容的及时更新。


如果使用了强缓存出现了BUG(项目更新之后,用户浏览器还在用旧文件,导致页面显示不正常)

  1. 资源文件名(指纹化)

    这是最彻底、最推荐的做法。原理是:如果文件内容变了,那么文件的名字也变,对于浏览器来说,这就是一个全新的资源,自然会发起新的请求。 实现方法是在文件名中嵌入一个"指纹"(Hash),这个指纹根据文件内容计算得出。内容一变,指纹必变。

    • 原始文件: styles.css
    • 指纹化后: styles.a1b2c3d4.css (哈希值随内容变化)

    如何实现指纹化?

    你通常不需要手动做这件事,现代前端构建工具(Webpack, Vite, Rollup, Parcel 等)可以自动完成。

    • Webpack: 使用 [contenthash] 占位符。

      javascript

      css 复制代码
      // webpack.config.js
      output: {
        filename: '[name].[contenthash].js',
        chunkFilename: '[name].[contenthash].chunk.js',
      }
    • Vite: 生产环境构建默认就会为静态资源添加哈希后缀。

      配套的 HTML 处理:

      构建工具在生成带哈希的文件名后,也会自动更新 HTML 中引用的路径,从 <link href="styles.css"> 变成 <link href="styles.a1b2c3d4.css">

      服务器配置:

      为你所有的静态资源(JS, CSS, 图片,字体等)设置长期强缓存。

      bash 复制代码
      # Nginx 配置示例
      location /static/ {
          alias /path/to/your/static/files/;
          # 设置一年强缓存
          expires 1y;
          add_header Cache-Control "public, immutable";
      }

      注意看,我们缓存的是 /static/ 目录下的文件,这些文件都是被指纹化的。非指纹化的文件(如 index.html绝对不能设置强缓存。

      总结与最佳实践流程

    方案 策略 适用场景 优点 缺点
    文件名指纹化 根本方案 所有新项目和生产环境 一劳永逸,完美平衡性能与更新 需要构建工具支持
    CDN 刷新 紧急预案 线上出现紧急 bug 生效快,针对性强 是补救措施,非预防手段
    查询字符串 临时方案 快速测试或紧急热修复 简单,无需工具链 缓存可靠性有争议
相关推荐
TimelessHaze15 分钟前
拆解字节面试题:async/await 到底是什么?底层实现 + 最佳实践全解析
前端·javascript·trae
执键行天涯1 小时前
从双重检查锁定的设计意图、锁的作用、第一次检查提升性能的原理三个角度,详细拆解单例模式的逻辑
java·前端·github
青青子衿越1 小时前
微信小程序web-view嵌套H5,小程序与H5通信
前端·微信小程序·小程序
OpenTiny社区1 小时前
TinyEngine 2.8版本正式发布:AI能力、区块管理、Docker部署一键强化,迈向智能时代!
前端·vue.js·低代码
程序员江鸟1 小时前
Java面试实战系列【JVM篇】- JVM内存结构与运行时数据区详解(私有区域)
java·jvm·面试
qfZYG1 小时前
Trae 编辑器在 Python 环境缺少 Pylance,怎么解决
前端·vue.js·编辑器
bug爱好者1 小时前
Vue3 基于Element Plus 的el-input,封装一个数字输入框组件
前端·javascript
Silence_xl1 小时前
RACSignal实现原理
前端
柯南二号1 小时前
【大前端】实现一个前端埋点SDK,并封装成NPM包
前端·arcgis·npm
dangkei1 小时前
【Wrangler(Cloudflare 的官方 CLI)和 npm/npx 的区别一次讲清】
前端·jvm·npm