web前端缓存命中监控方案
web前端缓存大致分为两种:
HTTP 网络缓存
浏览器默认的强缓存(Cache-Control/Expires)、协商缓存(304 Not Modified),针对所有静态资源(JS/CSS/ 图片 / 字体等)、接口请求
前端存储缓存
前端主动存储的缓存,比如 localStorage /sessionStorage/indexedDB / 内存缓存 (Map / 全局变量)
一、监控HTTP网络缓存命中
- 浏览器加载所有网络资源(js/css/img/ 接口)时,都会触发 HTTP 缓存规则
获取PerformanceResourceTiming 数据的方式
1、performance.getEntriesByType('resource')(主动获取)
作用:主动获取当前页面已经加载完成的「所有资源」的 PerformanceResourceTiming 对象数组。
2、performance.addEventListener('resourceentry') (实时监听)
作用:实时监听页面中「每一个资源加载完成」的事件,每加载完一个资源,就会触发一次该事件,回调中能拿到当前资源的 PerformanceResourceTiming 对象。
javascript
// 通用、精准、无歧义的缓存命中判断公式
function isHttpCacheHit(entry) {
// 命中缓存:实际下载字节为0,但是资源原始大小>0
return entry.transferSize === 0 && entry.decodedBodySize > 0;
}
transferSize : 从网络实际下载的字节大小
decodedBodySize: 资源解析后的原始实际大小
javascript
// 监听页面加载完成,确保所有资源都已加载完毕
window.addEventListener('load', () => {
// 浏览器兼容处理(极低版本浏览器兜底)
if (!window.performance || !window.performance.getEntriesByType) return;
// 1. 获取所有网络请求的性能数据(js/css/img/font/接口/ajax/fetch 全包含)
const allResources = performance.getEntriesByType('resource');
// 2. 初始化统计指标
const cacheStats = {
total: allResources.length, // 总资源数
hit: 0, // 缓存命中数
miss: 0, // 缓存未命中数
hitUrls: [], // 命中缓存的资源列表
missUrls: [], // 未命中缓存的资源列表
hitRate: 0 // 缓存命中率
};
// 3. 遍历所有资源,判断缓存命中状态
allResources.forEach(entry => {
const isHit = entry.transferSize === 0 && entry.decodedBodySize > 0;
if (isHit) {
cacheStats.hit++;
cacheStats.hitUrls.push(entry.name); // entry.name 是资源完整URL
} else {
cacheStats.miss++;
cacheStats.missUrls.push(entry.name);
}
});
// 4. 计算缓存命中率(保留2位小数)
cacheStats.hitRate = cacheStats.total > 0
? (cacheStats.hit / cacheStats.total * 100).toFixed(2) + '%'
: '0%';
// 5. 核心:打印统计结果(可替换为:上报到埋点系统)
console.log('【HTTP缓存命中统计】', cacheStats);
// 6. 可选:上报单条资源的缓存命中详情(精细化监控)
// reportToMonitor(cacheStats);
});
解释:
命中强缓存(200 from disk cache / 200 from memory cache):浏览器直接从本地磁盘 / 内存读取资源,不会发起任何网络请求,所以 transferSize = 0
命中协商缓存(304 Not Modified):浏览器会发起请求到服务端校验资源有效性,服务端返回 304 无响应体,实际下载的字节也为 0,所以 transferSize = 0
未命中缓存(200 OK):浏览器需要从服务端完整下载资源,transferSize > 0 且 约等于 decodedBodySize
跨域资源的数据获取限制
- 跨域资源的默认值都是 0
当页面加载的资源是跨域资源时(比如:CDN 上的图片 /js/css、第三方接口、跨域字体等),浏览器出于安全策略限制,会让 PerformanceResourceTiming 对象中的:
所有「体积属性」(decodedBodySize/encodedBodySize/transferSize) = 0
所有「时序属性」(dnsTime/tcpTime/ttfb 等) = 0
- 解决方案(两个条件缺一不可)
1、资源服务器端配置 CORS 响应头:返回 Timing-Allow-Origin: * 或 Timing-Allow-Origin: 你的页面域名(比如 https://www.xxx.com),表示允许该域名获取资源的时序性能数据
2、前端页面发起合规的跨域请求:如果是图片 / 脚本 / 样式等标签加载资源,需要给标签添加 crossorigin="anonymous" 属性(比如 <img src="跨域图片地址" crossorigin="anonymous">)
二、监控存储缓存命中(localStorage / 内存缓存等)
- 这类缓存是前端主动开发的缓存逻辑(浏览器不会自动统计),比如:用 localStorage 缓存接口数据、用 Map 做内存缓存、用 indexedDB 缓存大体积数据,这类缓存的命中监控逻辑:自己在缓存读写逻辑中加统计即可,因为缓存的读写完全由前端代码控制。
javascript
// 封装带缓存命中统计的localStorage工具类
const cacheStorage = {
stats: { hit: 0, miss: 0, hitRate: 0 }, // 缓存统计
// 读取缓存
get(key) {
const value = localStorage.getItem(key);
if (value) {
this.stats.hit++; // 命中+1
console.log(`localStorage缓存命中:${key}`);
} else {
this.stats.miss++; // 未命中+1
console.log(`localStorage缓存未命中:${key}`);
}
// 实时计算命中率
this.stats.hitRate = (this.stats.hit / (this.stats.hit + this.stats.miss) * 100).toFixed(2) + '%';
return value ? JSON.parse(value) : null;
},
// 写入缓存
set(key, value, expire = 0) {
localStorage.setItem(key, JSON.stringify({ value, expire: expire ? Date.now() + expire : 0 }));
},
// 获取统计数据
getStats() {
return this.stats;
}
};
// 使用示例
cacheStorage.set('user_info', { name: '张三' }, 3600 * 1000); // 缓存1小时
cacheStorage.get('user_info'); // 命中
cacheStorage.get('goods_list'); // 未命中
console.log('【localStorage缓存统计】', cacheStorage.getStats());