做了这么多年前端性能优化,我看别人网站的时候有个改不掉的习惯:打开 DevTools 的 Network 面板,逐个盯外部 JS 的响应头。见得多了就发现,有一类响应头组合出现频率极高——Cache-Control: max-age=31536000,有时候后面还跟着 immutable。不少同学第一次看到这个数字会犯嘀咕:一年 31536000 秒,哪来的?是随手写了个大数,还是背后有讲究?更常见的情况是:有人照着网上的教程配了,结果代码发布后用户死活看不到新版,一通排查下来发现就是这行头惹的祸。
这个标题本身透着一股典型的工程困惑:外部 JS 为什么可以设置 Cache-Control: max-age=31536000?设置之后到底发生了什么?为什么偏偏是 31536000,而不是 86400、2592000,或者干脆不设?这背后其实连着 HTTP 缓存协议、静态资源发布策略、CDN 行为、浏览器强缓存机制一整条链路。这篇文章就从协议到配置、从原理到事故现场,把这行字庖丁解牛式地拆开。适合前端开发、性能优化工程师、运维同学,也适合所有被“缓存不生效”和“缓存不更新”两头折磨的人。
1. 先从现象说起:31536000 到底是什么
1.1 一个普通用户视角的“一年”:打开页面看响应头
假如你随便打开一个大型网站,F12 切到 Network,刷新页面,随便点一个来自第三方域名的 JS 文件,比如统计脚本、广告 SDK、A/B 测试框架,大概率能在 Response Headers 里看到类似下面的内容:
code复制cache-control: public, max-age=31536000, immutable
这里 max-age=31536000 的单位是秒。31536000 秒 = 60 × 60 × 24 × 365 = 整整一年。意思是:这个资源从响应生成那一刻起,在未来一年内都可以被浏览器、CDN、代理服务器直接当成“新鲜”内容使用,不需要回源验证,也不需要重新下载。
你可能会问:一年?这么长不会出问题吗?正常来说,像 jquery.min.js、lodash、埋点 SDK 这类文件名固定、内容极低频变化的库文件,一年缓存非常合理。但这里有一个前提,所有真正做过性能优化的人心里都默认:这个 JS 文件的内容在一年内是“不可变”的,或者至少发布系统能保证内容变化时 URL 也跟着变。
1.2 为什么这类外部 JS 普遍能见到这个响应头
你平时自己开发的业务代码里,很少会看到有人给 app.js 直接配一年缓存。因为业务代码每星期都在改,如果配了一年,用户身上就会残留旧代码,等于发布了一次寂寞。
但“外部 JS”不一样。什么是外部 JS?准确说,是指那些不随你业务版本迭代而频繁变化、以独立脚本形式被页面引用的第三方或公共静态资源。它们的典型特征有三条:文件名长期不变、文件内容很少更新、被大量不同页面甚至不同站点复用。比如:
- 公共 CDN 上的开源库:jQuery、React、Vue 的 UMD 包;
- 第三方服务的 SDK:统计、监控、支付、客服聊天、广告等;
- 自己业务里的跨项目公共包:比如统一的埋点基础库、可视化上报脚本、多端共用的加密 JS。
这类 JS 如果每次刷新页面都重新向服务器请求,对带宽是巨大浪费,对页面加载性能也是伤害。给它们设置一年缓存,浏览器第二次访问时直接读本地缓存,连网络请求都不发,性能收益立竿见影。
1.3 “庖丁解牛”视角:这行头涉及几个技术层次
拆这行响应头,拆开的不仅是浏览器缓存规则。往下挖一层,一共涉及四个层次:
- HTTP 协议层:
Cache-Control是响应头标准里最核心的缓存指令之一,它决定了缓存参与者(浏览器、代理、CDN)如何对待这个资源; - 站点资源配置层:不是所有 JS 都能配一年,资源类型、变更频率、引用方式共同决定策略;
- 发布工程层:配了一年缓存之后,怎么在代码更新时让缓存自动“绕过”,这需要构建工具的产物指纹机制来兜底;
- 边缘节点层:如果资源走了 CDN,
max-age只是给浏览器看的,边
