前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库HTTP 缓存 fresh stale 验证 流程
HTHTTP缓存与代理

HTTP 缓存中「新鲜度」和「验证」分别解决什么问题,命中缓存的完整判定流程是怎样的?

新鲜度回答「这份缓存现在能不能直接用」,验证回答「过期后它是否还有效」。判定流程是先查新鲜期,fresh 直接复用,stale 再带验证器向源站确认,304 则更新缓存复用旧体。

前端进阶之旅 · 一题精讲更新于 2026.09.05
HTTP#缓存与代理#缓存
先看核心答案读代码示例
理解线索

两阶段判定的分工

  1. 新鲜度判什么用 max-age 或 Expires 对比 Age,决定是否直接复用
  2. 验证判什么过期后带验证器问源站,资源没变就继续用
  3. 命中的含义fresh 零请求命中,304 是验证后的二次命中

只有 fresh 阶段完全不耗网络;stale 后任何复用都必须先过验证这一关。

核心回答

先记住这个答案

HTTP 缓存采用两阶段模型。第一阶段是新鲜度判定:缓存根据 Expires 或 Cache-Control: max-age 计算响应的寿命,再结合当前 Age 判断是否 fresh,fresh 就直接返回本地副本,完全不发起网络请求。第二阶段是验证:缓存已 stale 时不立即丢弃,而是携带 If-None-Match 或 If-Modified-Since 发起条件请求,源站没变就回 304,缓存重置寿命继续复用旧响应体;变了就回 200 换新副本。新鲜度省掉的是整个请求,验证省掉的只是响应体传输。

  • 新鲜度决定是否发请求,验证决定能否复用旧体
  • fresh 直接命中零请求,stale 需条件请求确认
  • 304 只重置寿命,响应体仍用本地缓存

两阶段模型:新鲜度省请求,验证省传输

新鲜度解决的是「要不要发请求」的问题。缓存存下响应时同时记下它的寿命:HTTP/1.0 用 Expires 给出绝对过期时间,HTTP/1.1 用 Cache-Control: max-age=3600 给出相对秒数。每次复用前,缓存计算该响应当前的 Age,与寿命比较:未超期即为 fresh,直接把本地副本交给调用方,源站甚至不知道这次访问发生了。

验证解决的是「过期了还能不能用」的问题。stale 不等于作废,缓存会发起条件请求,把当初存下的验证器(ETag 或 Last-Modified)放进 If-None-Match 或 If-Modified-Since 带给源站。源站比对后若资源未变,返回 304 和新的缓存头,缓存把寿命重置、继续用旧响应体;若已变化则返回 200 完整新响应。所以验证省掉的是响应体传输和源站渲染,而非请求本身。

模拟两阶段命中判定JavaScript
function decide(stored, nowSec) {
  const age = nowSec - stored.storedAt;
  if (age < stored.maxAge) {
    return { action: 'fresh-hit', network: false };
  }
  if (stored.etag === null) {
    return { action: 'full-refetch', network: true };
  }
  return { action: 'revalidate', network: true, header: 'If-None-Match: ' + stored.etag };
}
const res = { storedAt: 1000, maxAge: 300, etag: '"abc"' };
console.log(JSON.stringify(decide(res, 1200)));
console.log(JSON.stringify(decide(res, 1400)));
console.log(JSON.stringify(decide({ storedAt: 1000, maxAge: 300, etag: null }, 1400)));
查看输出与解释
{"action":"fresh-hit","network":false}
{"action":"revalidate","network":true,"header":"If-None-Match: \"abc\""}
{"action":"full-refetch","network":true}

该脚本用存储时间与 maxAge 模拟新鲜度判定:未过期直接 fresh 命中不发请求;过期但有 ETag 走条件请求验证;过期且无验证器只能完整重取,对应真实缓存的三种走向。

静态资源一周内重复访问的实际走向

某图片响应带 Cache-Control: max-age=604800, public 和 ETag: "v3"。浏览器首次请求存下副本。第二天用户再次访问,缓存算得 Age 为一天,小于一周寿命,判定 fresh,直接从磁盘缓存返回,开发者工具里看不到任何网络请求,源站日志也没有记录。

第八天再访问时副本已 stale。浏览器发起带 If-None-Match: "v3" 的条件请求,源站发现图片未变,返回 304 并重申 max-age=604800,浏览器复用旧体并把寿命再延长一周。若期间运营换了图,ETag 变为 "v4",源站返回 200 新图,缓存替换副本。整个流程里,新鲜度让七天内的访问零成本,验证让过期后的确认只需一个头部往返。

判定失效的常见前提与代价

两阶段模型有几个容易踩空的前提。其一,响应根本没有 Expires 或 max-age 时无法精确判 fresh,只能依赖启发式缓存或每次都验证。其二,响应若没带任何验证器,stale 后无法发条件请求,只能完整重取,验证阶段的节省完全失效。其三,no-store 响应根本不落盘,谈不上命中;no-cache 则强制每次都走验证。

对应处理是:所有可缓存响应显式给出 Cache-Control 和 ETag,不要依赖默认行为;对不允许任何 stale 复用的接口加 no-cache,代价是每次访问都有一次验证往返;另外共享缓存还受 s-maxage 与 private 影响,同一份响应在 CDN 和浏览器里的新鲜期可能不同,排查命中问题时要分清是哪一级缓存的判定。

回答前,多想一步

容易答错的地方

过期(stale)等于缓存作废、必须重新下载
错。stale 只表示新鲜期结束,缓存仍持有旧副本,可以发起条件请求验证;源站返回 304 后旧副本被重新标记为 fresh 继续使用,只有验证失败才需要拉取新响应体。
命中缓存就意味着完全没发请求
不准确。fresh 命中确实零网络请求,但 stale 后经 304 验证复用旧体也算缓存命中,此时有一次到源站的条件请求往返,只是省掉了响应体传输,两类命中的成本差别很大。
试着用自己的话回答

面试官还会怎么问?

max-age 和 Expires 同时出现时以哪个为准?

max-age 优先。它是相对秒数,不受客户端时钟误差影响;Expires 是绝对时间,依赖本机时钟准确,时钟被改会导致新鲜度误判,因此 HTTP/1.1 规定两者并存时忽略 Expires。

304 之后缓存头会更新吗?

会。304 响应可以携带新的 Cache-Control、ETag 等头部,缓存必须用它们更新已存条目的元数据,从而重置新鲜期,旧响应体原样保留继续复用。

源站宕机时 stale 缓存还能用吗?

默认不能,验证失败请求就失败。若响应带 stale-if-error,缓存在源站 5xx 或不可达的指定窗口内可以直接返回 stale 副本,这是用内容可能略旧换取可用性的显式授权。

从一道题,走向一组知识

把知识连起来

缓存与代理

服务器没有返回 Expires 和 max-age 时,HTTP 缓存能否根据 Last-Modified 推断新鲜期,风险是什么?

同属「缓存与代理」专题,接着看 HTTP 启发式缓存 heuristic freshness Last-Modified 在具体场景中的处理方式。

缓存与代理

HTTP 缓存如何计算响应的当前 Age,经过多级代理时 Age 头与 Date 头怎样参与计算?

同属「缓存与代理」专题,接着看 HTTP Age 头计算 多级代理 Date RFC9111 在具体场景中的处理方式。

参考资料

  • HTTP caching

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 两阶段模型:新鲜度省请求,验证省传输
  3. 静态资源一周内重复访问的实际走向
  4. 判定失效的常见前提与代价
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑