作为一名在性能优化领域有多年经验的从业者,本文用更自然的方式讲解如何通过 Performance API 分析资源缓存命中率。
核心思路是:虽然无法直接从浏览器获取一个名为“缓存命中”的布尔值,但通过 `PerformanceResourceTiming` 接口中的 **transferSize** 和 **decodedBodySize** 等字段,结合响应头与加载行为,完全可以精准推断资源是否命中了缓存。

### 用 transferSize 判断本地缓存是否生效
`transferSize` 字段记录实际从网络接收的字节数,是判断逻辑中最可靠的基石。当出现以下情况时:
* **`transferSize === 0` 且 `decodedBodySize > 0`**:几乎可以确定该资源直接从内存或磁盘缓存中取出(即强缓存命中)。
* **`transferSize > 0`,但远小于首次加载时的数值,且响应状态码是 `304 Not Modified`**:说明浏览器携带资源与服务器进行了协商,服务器确认资源未变,仅返回 304 状态码,协商缓存生效。
* **`transferSize` 接近 `decodedBodySize`,且状态码是 `200`**:这次资源是完完整整从网络下载的,缓存未发挥作用。
### 结合 X-Cache 响应头识别 CDN 或 Service Worker 缓存
浏览器本身不会直接告知资源来自 CDN 还是 Service Worker,但服务端可以。关键在于查看响应头。
* 如果项目使用了 Service Worker,可以在拦截 fetch 请求后,为响应添加自定义头,例如 `response.headers.set('X-Cache-Hit', 'true')`。
* 大型 CDN 厂商会提供标准化的头信息,如 Cloudflare 的 `x-cache: HIT`,阿里云的 `x-cdn-cache: HIT`。
* 在页面中,通过 `performance.getEntriesByType('resource')` 获取资源条目后,再配合 `fetch()` 或 `Response.clone().headers` 读取这些响应头。需要留意的是,这仅限于同源或 CORS 允许的请求。
### 统计并计算缓存命中率
在页面完全加载完成后(合适的时机是监听 `window.addEventListener('load', ...)`),可以遍历所有资源条目:
* **总请求数**:即 `performance.getEntriesByType('resource').length`。
* **命中数**:统计所有满足 `entry.transferSize === 0 && entry.decodedBodySize > 0` 条件的条目数。
* **命中率**:通过 `(hitCount / totalCount * 100).toFixed(2) + '%'` 计算得出。
* 更精细的做法是:按资源类型(js / css / img)分组记录命中情况,这样可以快速定位哪类资源的缓存策略存在问题。
### 关联 Navigation Timing 验证真实性能收益
仅看命中率的数字还不够,需要将其与实际速度挂钩。
* 调用 `performance.getEntriesByType('navigation')[0]` 获取页面首屏的关键时序。
* 对比 `domContentLoadedEventEnd` 或 `loadEventEnd` 这两个指标,观察缓存命中与未命中场景下的差异。
* 尤其要关注影响 LCP(最大内容绘制)的关键资源。如果其 `entry.transferSize === 0`,再检查 `renderTime`。通常情况下,会发现它比未命中缓存时提前 200 到 800 毫秒渲染出来——这才是实打实的性能收益。