当前位置: 首页 > news >正文

《打破 “慢“ 的黑箱:前端请求全链路耗时统计方案》

接续上文:网站酷炫换皮肤?——PC 端 H5 换肤方案实战分享-CSDN博客

 主页: 有更多有趣的实例教程哟!!!!!!!

专栏有更多内容:

https://blog.csdn.net/m0_73589512/category_13028539.html

目录

《打破 "慢" 的黑箱:前端请求全链路耗时统计方案》

一、为什么需要请求耗时统计工具?

二、核心需求拆解:我们需要统计哪些数据?

三、技术方案设计:怎么捕获这些数据?

3.1 捕获 XMLHttpRequest 请求

3.2 捕获 Fetch API 请求

3.3 捕获页面资源请求

3.4 统一的数据收集函数

四、数据存储与上报:怎么保存和传输数据?

4.1 本地存储:localStorage 暂存

4.2 数据上报:发送到后端

五、数据可视化:让数据一目了然

5.1 简单的面板 UI

5.2 数据展示组件

5.3 高级可视化:添加图表

六、高级功能:让工具更实用

6.1 过滤和搜索

6.2 性能预警

6.3 环境区分

七、总结与扩展

可以扩展的功能:

八、实战部署:从代码到产品

8.1 模块化封装

8.2 按需加载

8.3 性能影响控制

九、结语:让数据驱动性能优化

《打破 "慢" 的黑箱:前端请求全链路耗时统计方案》

在前端开发的日常中,我们总会遇到这样的灵魂拷问:

产品经理:"用户说页面加载好慢啊!赶紧优化一下!" 你:"有多慢?具体哪个接口慢?" 产品经理:"用户就是说慢啊!你自己感受一下嘛!" 你内心 OS:我感受个锤子哦!没有数据我优化个寂寞?

这种 "凭感觉" 的性能优化,就像闭着眼睛修水管 —— 运气好能堵住漏水,运气不好能捅出喷泉。今天我们就来设计一个 "全站请求耗时统计工具",用数据说话,让 "慢" 这个模糊的概念变得清晰可量化。

一、为什么需要请求耗时统计工具?

在开始搬砖之前,我们得先搞明白:为什么要费力气做这个工具?

想象一下,你开了家奶茶店,顾客总说 "等太久了"。如果你不知道:

  • 是点单环节慢?

  • 是制作环节慢?

  • 还是某个特定饮品(比如杨枝甘露这种配料复杂的)拖慢了整体速度?

那你的优化方案大概率是瞎折腾 —— 可能给点单员加了速,结果发现问题出在制作台效率上。

前端请求也是一个道理。一个页面从打开到能交互,可能要发十几个甚至几十个请求:HTML、CSS、JS 等静态资源,接口数据、图片、埋点上报... 哪个请求慢了?是网络问题还是服务器问题?是偶尔慢还是一直慢?没有统计工具,这些问题永远是谜。

一个靠谱的请求统计工具能帮我们解决这些问题

  • 精确记录每个请求的耗时(从发起到完成的全流程)

  • 区分请求类型(接口、静态资源、图片等)

  • 标记异常请求(超时、失败的)

  • 聚合分析数据(哪个接口平均耗时最长?哪个时段请求最慢?)

  • 前端可视化展示(用图表让数据一目了然)

有了这些数据,优化性能就能有的放矢 —— 就像医生看病先做检查,而不是凭感觉开药方。

二、核心需求拆解:我们需要统计哪些数据?

设计工具前,先明确我们要收集哪些信息。就像做问卷调查前要设计问题,不能想到啥加啥。

一个 HTTP 请求从发出到完成,有很多关键节点,我们需要记录的核心数据包括:

数据项作用示例
唯一标识区分不同请求"req_1629273845621_324"
请求地址知道是哪个请求"https://api.example.com/userinfo"
请求类型区分接口 / 静态资源"xhr"(接口)、"fetch"(接口)、"resource"(静态资源)
资源类型更细的分类"script"(JS 文件)、"image"(图片)、"font"(字体)
方法请求方式"GET"、"POST"
状态码判断请求是否成功200(成功)、404(未找到)、500(服务器错误)
开始时间计算耗时的起点1629273845621(时间戳)
结束时间计算耗时的终点1629273846123(时间戳)
耗时核心指标(结束 - 开始)502ms
发起位置知道是哪个页面 / 组件发起的"首页 /index.vue"、"购物车组件"
错误信息失败时记录原因"Network Error"、"Timeout"
环境信息辅助分析(是否和环境有关)"production"(生产环境)、"Chrome 92"

这些数据就像给请求拍了个 "全身照",既能看到整体耗时,又能分析具体细节。比如发现某个接口平均耗时 2 秒,但只在 Chrome 浏览器上出现,那可能是浏览器兼容性导致的问题。

三、技术方案设计:怎么捕获这些数据?

明确了要统计什么,接下来就是怎么实现 —— 怎么 "抓" 到这些请求的数据。

前端的请求主要有三种类型:

  1. XMLHttpRequest(传统的 Ajax 请求)

  2. Fetch API(现代的请求方式)

  3. 页面资源请求(JS、CSS、图片等)

我们需要针对这三种类型分别设计捕获方案。

3.1 捕获 XMLHttpRequest 请求

XMLHttpRequest 是老古董了,但现在还有很多项目在⽤。它的特点是有明确的事件和方法,我们可以通过 "重写" 它的方法来实现监控。

原理很简单:就像给快递盒贴了个追踪器,原本的快递运输流程不变,但我们能知道它什么时候发出、什么时候签收。

// 保存原生的XMLHttpRequest
const originalXHR = window.XMLHttpRequest;
​
// 重写XMLHttpRequest
window.XMLHttpRequest = function() {const xhr = new originalXHR();let requestData = {id: `xhr_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, // 生成唯一IDurl: '',method: '',startTime: 0,endTime: 0,duration: 0,status: 0,type: 'xhr',error: ''};
​// 监听open方法,获取请求地址和方法const originalOpen = xhr.open;xhr.open = function(method, url) {requestData.method = method;requestData.url = url;originalOpen.apply(xhr, arguments); // 调用原生方法};
​// 监听send方法,记录开始时间const originalSend = xhr.send;xhr.send = function() {requestData.startTime = Date.now();originalSend.apply(xhr, arguments);};
​// 监听load事件,记录成功的请求xhr.addEventListener('load', function() {requestData.endTime = Date.now();requestData.duration = requestData.endTime - requestData.startTime;requestData.status = xhr.status;collectRequestData(requestData); // 收集数据});
​// 监听error事件,记录失败的请求xhr.addEventListener('error', function() {requestData.endTime = Date.now();requestData.duration = requestData.endTime - requestData.startTime;requestData.status = xhr.status;requestData.error = 'Network Error';collectRequestData(requestData);});
​// 监听abort事件,记录被取消的请求xhr.addEventListener('abort', function() {requestData.endTime = Date.now();requestData.duration = requestData.endTime - requestData.startTime;requestData.status = 0;requestData.error = 'Request Aborted';collectRequestData(requestData);});
​return xhr;
};

这段代码的核心是 "代理模式"—— 我们没有改变 XMLHttpRequest 的功能,只是在它的关键节点(打开、发送、完成、失败)添加了数据收集的逻辑。就像给原本的管道装了个流量计,不影响水流,但能知道流量多少。

3.2 捕获 Fetch API 请求

Fetch API 是 ES6 新增的请求方式,基于 Promise,比 XMLHttpRequest 更现代。捕获它的思路和 XHR 类似,但实现方式略有不同。

// 保存原生的fetch
const originalFetch = window.fetch;
​
// 重写fetch
window.fetch = function(input, init = {}) {// 处理请求地址(input可能是Request对象或URL字符串)const url = typeof input === 'string' ? input : input.url;const method = (init.method || 'GET').toUpperCase();
​const requestData = {id: `fetch_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,url: url,method: method,startTime: Date.now(),endTime: 0,duration: 0,status: 0,type: 'fetch',error: ''};
​// 调用原生fetch,并在Promise resolved/rejected时收集数据return originalFetch(input, init).then(response => {// 克隆响应对象(因为response.body只能读取一次)const clonedResponse = response.clone();requestData.endTime = Date.now();requestData.duration = requestData.endTime - requestData.startTime;requestData.status = clonedResponse.status;collectRequestData(requestData);return response; // 不影响原请求的响应}).catch(error => {requestData.endTime = Date.now();requestData.duration = requestData.endTime - requestData.startTime;requestData.error = error.message || 'Fetch Error';collectRequestData(requestData);throw error; // 不影响原请求的错误处理});
};

Fetch 的监控和 XHR 类似,但因为它返回 Promise,所以我们通过链式调用的方式在请求完成或失败时收集数据。这里要注意克隆 response 对象,因为 response 的 body 是可读流,只能读取一次,不克隆的话会影响原请求的处理。

3.3 捕获页面资源请求

页面加载时会请求各种资源:JS、CSS、图片、字体等。这些请求可以通过 Performance API 来捕获。

Performance API 就像一个系统日志,记录了页面加载过程中各种事件的时间戳。我们可以通过它获取所有资源的加载信息。

// 监听页面加载完成事件(此时大部分资源已加载)
window.addEventListener('load', collectResourceData);
​
// 也可以定时检查新加载的资源
setInterval(collectResourceData, 1000);
​
function collectResourceData() {// 获取所有资源的性能数据const performanceEntries = performance.getEntriesByType('resource');performanceEntries.forEach(entry => {// 过滤已处理过的资源(避免重复收集)if (entry._tracked) return;entry._tracked = true;
​const requestData = {id: `resource_${entry.name}_${Date.now()}`,url: entry.name,method: 'GET', // 资源请求一般是GETstartTime: entry.startTime,endTime: entry.responseEnd,duration: entry.responseEnd - entry.startTime,status: 200, // 资源请求成功默认200(实际可能有失败,这里简化处理)type: 'resource',resourceType: entry.initiatorType, // 资源类型:script、link、img等error: ''};
​// 简单判断资源是否加载失败(实际场景可能需要更复杂的判断)if (requestData.duration < 1 && entry.responseEnd === 0) {requestData.error = 'Resource Load Failed';}
​collectRequestData(requestData);});
}

这里用performance.getEntriesByType('resource')获取所有资源的加载信息,包括开始时间、结束时间、资源类型等。需要注意的是,资源加载是异步的,所以我们可以在页面加载完成后收集一次,再定时检查新加载的资源(比如懒加载的图片)。

3.4 统一的数据收集函数

上面三种捕获方式最后都会调用collectRequestData函数,我们需要实现这个函数来统一处理数据:

// 存储所有请求数据的数组
const allRequests = [];
​
// 数据收集函数
function collectRequestData(data) {// 补充环境信息const envInfo = {timestamp: Date.now(), // 记录数据收集的时间page: window.location.href, // 当前页面URLuserAgent: navigator.userAgent, // 浏览器信息network: navigator.connection ? navigator.connection.effectiveType : 'unknown' // 网络类型(4g/3g等)};
​// 合并数据const request = { ...data, ...envInfo };// 添加到数组allRequests.push(request);// 控制台打印(开发环境用)console.log(`[请求统计] ${request.method} ${request.url} 耗时: ${request.duration}ms 状态: ${request.status}`);// 可以在这里添加数据上报逻辑(发送到后端)// reportToServer(request);// 保持数组不要太大,超过1000条就清空(避免内存占用过多)if (allRequests.length > 1000) {allRequests.splice(0, allRequests.length - 500);}
}

这个函数做了几件事:

  1. 补充环境信息(当前页面、浏览器、网络类型等)

  2. 合并数据并存储到数组中

  3. 控制台打印(方便开发时查看)

  4. 简单的内存管理(避免数组无限增长)

四、数据存储与上报:怎么保存和传输数据?

收集了数据,接下来要考虑怎么存储和上报。如果只是在控制台看,刷新页面就没了,没什么用。

4.1 本地存储:localStorage 暂存

可以用 localStorage 暂时存储数据,避免页面刷新丢失:

// 保存数据到localStorage
function saveToLocalStorage() {try {localStorage.setItem('requestStats', JSON.stringify(allRequests));} catch (e) {console.warn('本地存储请求数据失败', e);}
}
​
// 从localStorage恢复数据
function loadFromLocalStorage() {try {const saved = localStorage.getItem('requestStats');if (saved) {allRequests.push(...JSON.parse(saved));}} catch (e) {console.warn('恢复本地请求数据失败', e);}
}
​
// 初始化时恢复数据
loadFromLocalStorage();
​
// 定时保存数据
setInterval(saveToLocalStorage, 5000);

注意 localStorage 有容量限制(一般是 5MB),所以不能存太多数据。可以定期清理旧数据,只保留最近的请求。

4.2 数据上报:发送到后端

本地存储只能在当前设备查看,要实现多设备、多用户的数据聚合分析,需要把数据上报到后端:

// 数据上报函数
function reportToServer(data) {// 避免上报请求本身被统计(否则会陷入无限循环)if (data.url.includes('/api/report')) {return;}
​// 用Image对象发送请求(比XHR/fetch更轻量,不阻塞页面)const img = new Image();const reportUrl = 'https://api.example.com/report/request-stats';// 构造上报参数(用JSON.stringify转成字符串)const params = new URLSearchParams();params.append('data', JSON.stringify(data));img.src = `${reportUrl}?${params.toString()}`;// 简单的错误处理img.onerror = () => {console.warn('数据上报失败', reportUrl);};
}

这里用 Image 对象发送上报请求,比 XHR 或 fetch 更轻量,而且不会阻塞页面。需要注意排除上报请求本身,否则上报请求会被统计,然后又触发上报,陷入无限循环。

实际项目中,可能需要批量上报(而不是每个请求都上报),可以攒一批数据再发送,减少请求次数:

// 批量上报队列
const reportQueue = [];
​
// 批量上报函数
function batchReport() {if (reportQueue.length === 0) return;// 取出队列中的所有数据const dataToReport = [...reportQueue];reportQueue.length = 0;// 发送批量上报请求const img = new Image();const reportUrl = 'https://api.example.com/report/batch-request-stats';const params = new URLSearchParams();params.append('data', JSON.stringify(dataToReport));img.src = `${reportUrl}?${params.toString()}`;
}
​
// 修改collectRequestData,把数据加入队列
function collectRequestData(data) {// ... 前面的逻辑不变 ...// 加入上报队列reportQueue.push(request);// 每满10条或每30秒上报一次if (reportQueue.length >= 10) {batchReport();} else {// 防抖:30秒内没满10条也上报clearTimeout(window.reportTimer);window.reportTimer = setTimeout(batchReport, 30000);}
}

批量上报能减少网络请求,更适合生产环境。

五、数据可视化:让数据一目了然

有了数据,还需要直观的展示方式。光看一堆 JSON 数据,谁也看不出所以然来。我们可以在页面上添加一个可视化面板,展示请求统计信息。

5.1 简单的面板 UI

先创建一个悬浮的面板,点击可以展开查看详情:

<!-- 在页面中添加这个div -->
<div id="requestStatsPanel" style="position: fixed; bottom: 20px; right: 20px; z-index: 9999; background: white; border: 1px solid #ccc; border-radius: 4px; padding: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);"><div style="cursor: pointer; font-weight: bold;" id="panelHeader">请求统计 (点击展开)</div><div id="panelContent" style="display: none; max-height: 400px; overflow-y: auto; margin-top: 10px; width: 600px;"><!-- 统计内容会在这里动态生成 --></div>
</div>

然后写点 JS 控制面板的展开 / 折叠:

// 面板交互
const panelHeader = document.getElementById('panelHeader');
const panelContent = document.getElementById('panelContent');
​
panelHeader.addEventListener('click', () => {panelContent.style.display = panelContent.style.display === 'none' ? 'block' : 'none';panelHeader.textContent = panelContent.style.display === 'none' ? '请求统计 (点击展开)' : '请求统计 (点击收起)';// 展开时更新数据updatePanelContent();
});

5.2 数据展示组件

我们需要展示的数据包括:

  • 总请求数、成功数、失败数

  • 平均耗时、最长耗时、最短耗时

  • 按类型分组的统计(XHR、Fetch、资源)

  • 最近的请求列表

先来实现一个更新面板内容的函数:

function updatePanelContent() {if (allRequests.length === 0) {panelContent.innerHTML = '<p>暂无请求数据</p>';return;}
​// 计算统计指标const stats = calculateStats(allRequests);// 生成HTMLlet html = `<div style="margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px dashed #eee;"><h4>总体统计</h4><div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;"><div>总请求: <strong>${stats.total}</strong></div><div>成功: <strong style="color: green;">${stats.success}</strong></div><div>失败: <strong style="color: red;">${stats.failed}</strong></div><div>平均耗时: <strong>${stats.avgDuration}ms</strong></div><div>最长耗时: <strong>${stats.maxDuration}ms</strong></div><div>最短耗时: <strong>${stats.minDuration}ms</strong></div></div></div>
​<div style="margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px dashed #eee;"><h4>按类型统计</h4><div style="display: flex; gap: 15px;">`;
​// 按类型添加统计Object.keys(stats.byType).forEach(type => {const typeStats = stats.byType[type];html += `<div><strong>${type.toUpperCase()}</strong>: ${typeStats.count}个<br>平均: ${typeStats.avgDuration}ms</div>`;});
​html += `</div></div>
​<div><h4>最近10条请求</h4><table style="width: 100%; border-collapse: collapse; font-size: 12px;"><thead><tr style="background: #f5f5f5;"><th style="border: 1px solid #eee; padding: 5px; text-align: left;">方法</th><th style="border: 1px solid #eee; padding: 5px; text-align: left; width: 30%;">URL</th><th style="border: 1px solid #eee; padding: 5px; text-align: center;">耗时</th><th style="border: 1px solid #eee; padding: 5px; text-align: center;">状态</th></tr></thead><tbody>`;
​// 添加最近10条请求const recentRequests = [...allRequests].reverse().slice(0, 10);recentRequests.forEach(req => {const statusClass = req.status === 200 ? 'color: green;' : 'color: red;';const durationClass = req.duration > 1000 ? 'color: red;' : (req.duration > 500 ? 'color: orange;' : '');html += `<tr><td style="border: 1px solid #eee; padding: 5px;">${req.method}</td><td style="border: 1px solid #eee; padding: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${req.url}</td><td style="border: 1px solid #eee; padding: 5px; text-align: center; ${durationClass}">${req.duration}ms</td><td style="border: 1px solid #eee; padding: 5px; text-align: center; ${statusClass}">${req.status || req.error}</td></tr>`;});
​html += `</tbody></table></div>`;
​panelContent.innerHTML = html;
}
​
// 计算统计指标的函数
function calculateStats(requests) {const total = requests.length;const success = requests.filter(r => r.status >= 200 && r.status < 300 && !r.error).length;const failed = total - success;const durations = requests.map(r => r.duration);const avgDuration = Math.round(durations.reduce((sum, d) => sum + d, 0) / total);const maxDuration = Math.max(...durations);const minDuration = Math.min(...durations);// 按类型统计const byType = {};requests.forEach(req => {if (!byType[req.type]) {byType[req.type] = { count: 0, totalDuration: 0, avgDuration: 0 };}byType[req.type].count++;byType[req.type].totalDuration += req.duration;});// 计算每种类型的平均耗时Object.keys(byType).forEach(type => {byType[type].avgDuration = Math.round(byType[type].totalDuration / byType[type].count);});return {total,success,failed,avgDuration,maxDuration,minDuration,byType};
}

这个面板会显示总体统计、按类型统计和最近的请求列表,还会用不同颜色标记耗时过长(>1000ms 标红,>500ms 标橙)和失败的请求,一眼就能看出问题。

5.3 高级可视化:添加图表

如果想更直观地展示数据趋势,可以引入 Chart.js 等图表库。先引入 Chart.js:

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

然后在面板中添加一个图表容器:

<!-- 在panelContent中添加 -->
<div style="margin-bottom: 15px;"><h4>耗时分布</h4><canvas id="durationChart" height="200"></canvas>
</div>

再写代码生成图表:

function updateCharts() {const ctx = document.getElementById('durationChart').getContext('2d');// 按类型分组的耗时数据const typeData = {};allRequests.forEach(req => {if (!typeData[req.type]) {typeData[req.type] = [];}typeData[req.type].push(req.duration);});// 准备图表数据const labels = Object.keys(typeData);const data = labels.map(type => {// 计算每种类型的平均耗时const avg = typeData[type].reduce((sum, d) => sum + d, 0) / typeData[type].length;return Math.round(avg);});// 颜色const backgroundColors = ['rgba(255, 99, 132, 0.5)','rgba(54, 162, 235, 0.5)','rgba(255, 206, 86, 0.5)'];// 销毁旧图表(如果存在)if (window.durationChart) {window.durationChart.destroy();}// 创建新图表window.durationChart = new Chart(ctx, {type: 'bar',data: {labels: labels.map(t => t.toUpperCase()),datasets: [{label: '平均耗时 (ms)',data: data,backgroundColor: backgroundColors.slice(0, labels.length),borderColor: backgroundColors.map(c => c.replace('0.5', '1')),borderWidth: 1}]},options: {scales: {y: {beginAtZero: true,title: {display: true,text: '耗时 (ms)'}}},plugins: {tooltip: {callbacks: {label: function(context) {return `平均耗时: ${context.raw}ms`;}}}}}});
}
​
// 在updatePanelContent的最后调用updateCharts
function updatePanelContent() {// ... 前面的代码 ...updateCharts();
}

这样就会生成一个柱状图,展示不同类型请求的平均耗时,一眼就能看出哪种类型的请求最慢。

六、高级功能:让工具更实用

基础功能完成后,我们可以添加一些高级功能,让工具更实用。

6.1 过滤和搜索

当请求很多时,想找到特定的请求很麻烦,可以添加过滤和搜索功能:

<!-- 在panelContent顶部添加 -->
<div style="margin-bottom: 10px; display: flex; gap: 10px; align-items: center;"><input type="text" id="searchInput" placeholder="搜索URL..." style="flex: 1; padding: 5px;"><select id="filterType" style="padding: 5px;"><option value="all">所有类型</option><option value="xhr">XHR</option><option value="fetch">Fetch</option><option value="resource">资源</option></select><select id="filterStatus" style="padding: 5px;"><option value="all">所有状态</option><option value="success">成功</option><option value="failed">失败</option></select>
</div>

然后添加过滤逻辑:

// 监听搜索和过滤变化
document.getElementById('searchInput').addEventListener('input', updateFilteredRequests);
document.getElementById('filterType').addEventListener('change', updateFilteredRequests);
document.getElementById('filterStatus').addEventListener('change', updateFilteredRequests);
​
// 存储过滤后的请求
let filteredRequests = [...allRequests];
​
function updateFilteredRequests() {const searchText = document.getElementById('searchInput').value.toLowerCase();const filterType = document.getElementById('filterType').value;const filterStatus = document.getElementById('filterStatus').value;filteredRequests = allRequests.filter(req => {// 搜索URLconst matchesSearch = req.url.toLowerCase().includes(searchText);// 过滤类型const matchesType = filterType === 'all' || req.type === filterType;// 过滤状态let matchesStatus = true;if (filterStatus === 'success') {matchesStatus = req.status >= 200 && req.status < 300 && !req.error;} else if (filterStatus === 'failed') {matchesStatus = !(req.status >= 200 && req.status < 300 && !req.error);}return matchesSearch && matchesType && matchesStatus;});// 更新面板内容(需要修改updatePanelContent使用filteredRequests)updatePanelContent();
}
​
// 修改calculateStats和updatePanelContent,使用filteredRequests而不是allRequests
function updatePanelContent() {if (filteredRequests.length === 0) {panelContent.innerHTML = '<p>没有匹配的请求数据</p>';return;}// ... 其余代码使用filteredRequests ...
}
​
function calculateStats(requests) {// 保持不变,但传入的是filteredRequests
}

现在用户可以通过 URL 关键词、请求类型、成功 / 失败状态来过滤请求,查找特定请求变得很方便。

6.2 性能预警

当请求耗时超过阈值(比如 2 秒)时,可以自动提醒开发者:

// 预警阈值(毫秒)
const WARNING_THRESHOLD = 2000;
​
// 修改collectRequestData,添加预警逻辑
function collectRequestData(data) {// ... 前面的代码 ...// 如果耗时超过阈值,发出预警if (request.duration > WARNING_THRESHOLD) {console.warn(`[性能预警] 请求 ${request.method} ${request.url} 耗时过长: ${request.duration}ms`);// 在面板上显示预警showWarning(`请求 ${request.url} 耗时过长 (${request.duration}ms)`);}// ... 后面的代码 ...
}
​
// 显示预警的函数
function showWarning(message) {const warningDiv = document.createElement('div');warningDiv.style.cssText = `position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: #fff3cd; color: #856404; padding: 10px 20px; border: 1px solid #ffeeba;border-radius: 4px; z-index: 10000; animation: fadein 0.5s;`;warningDiv.textContent = message;document.body.appendChild(warningDiv);// 3秒后自动消失setTimeout(() => {warningDiv.style.opacity = '0';warningDiv.style.transition = 'opacity 0.5s';setTimeout(() => warningDiv.remove(), 500);}, 3000);
}

这样当有请求耗时过长时,会在控制台警告并在页面顶部显示提示,开发者能及时发现问题。

6.3 环境区分

在开发环境需要详细的统计,但生产环境可能只需要上报关键数据,避免影响用户体验:

// 环境变量(实际项目中可以通过构建工具注入)
const ENV = 'development'; // 'development' 或 'production'
​
// 修改collectRequestData,根据环境决定是否显示控制台日志
function collectRequestData(data) {// ... 前面的代码 ...// 开发环境才在控制台打印if (ENV === 'development') {console.log(`[请求统计] ${request.method} ${request.url} 耗时: ${request.duration}ms 状态: ${request.status}`);}// 生产环境只上报,不显示面板if (ENV === 'production') {document.getElementById('requestStatsPanel').style.display = 'none';}// ... 后面的代码 ...
}

这样既能在开发时方便调试,又不会在生产环境打扰用户。

七、总结与扩展

到这里,一个功能完整的全站请求耗时统计工具就设计完成了。它能:

  • 捕获 XHR、Fetch、资源三种类型的请求

  • 记录详细的请求信息和环境数据

  • 本地存储和批量上报数据

  • 可视化展示统计结果和图表

  • 支持过滤搜索和性能预警

这个工具就像给网站装了个 "速度仪表盘",让原本模糊的 "慢" 变得可量化、可分析。以后产品经理再说 "用户说慢",你就可以打开面板,指着数据说:"根据统计,首页的 banner 图片平均加载需要 2.3 秒,是主要瓶颈,我们可以优化图片大小"—— 有理有据,再也不用凭感觉优化了。

可以扩展的功能:

  1. 更详细的错误分析(记录错误堆栈、响应内容)

  2. 与后端配合实现多用户数据聚合分析

  3. 自定义阈值配置:允许开发者根据接口特性设置不同的耗时阈值(比如文件上传接口允许 5 秒,普通接口限制 2 秒)。

    // 自定义阈值配置
    const THRESHOLD_CONFIG = {default: 2000, // 默认阈值'/api/upload': 5000, // 上传接口放宽到5秒'/api/large-data': 3000 // 大数据接口放宽到3秒
    };
    ​
    // 修改预警逻辑,支持自定义阈值
    function getThresholdByUrl(url) {// 查找是否有匹配的自定义阈值const matchedKey = Object.keys(THRESHOLD_CONFIG).find(key => url.includes(key));return matchedKey ? THRESHOLD_CONFIG[matchedKey] : THRESHOLD_CONFIG.default;
    }
    ​
    // 在collectRequestData中使用
    if (request.duration > getThresholdByUrl(request.url)) {console.warn(`[性能预警] 请求 ${request.method} ${request.url} 耗时过长: ${request.duration}ms`);showWarning(`请求 ${request.url} 耗时过长 (${request.duration}ms)`);
    }
    1. 用户行为关联:记录请求发生时的用户操作(如 "点击登录按钮后发起的 /login 请求"),便于定位问题场景。

      // 记录用户行为的函数
      let lastAction = '';
      function recordUserAction(action) {lastAction = `${new Date().toLocaleTimeString()}:${action}`;
      }
      ​
      // 在关键用户操作处调用
      document.getElementById('loginBtn').addEventListener('click', () => {recordUserAction('点击登录按钮');
      });
      ​
      // 在请求数据中添加用户行为
      function collectRequestData(data) {// ...const request = { ...data, ...envInfo,userAction: lastAction // 关联最近的用户行为};// ...
      }
    2. 性能趋势分析:存储多天的统计数据,生成日 / 周 / 月趋势图表,观察性能变化。

    八、实战部署:从代码到产品

    设计完工具后,还需要考虑如何在实际项目中部署使用。以下是一些实用建议:

    8.1 模块化封装

    将工具封装成独立模块,方便在多个项目中复用:

    // request-stats.js
    export default class RequestStats {constructor(options = {}) {this.options = {env: 'development',threshold: 2000,reportUrl: 'https://api.example.com/report',...options};this.allRequests = [];this.init();}
    ​// 初始化监控init() {this.hookXHR();this.hookFetch();this.monitorResources();if (this.options.env === 'development') {this.createPanel();}}
    ​// XHR监控方法hookXHR() { /* ... 之前的XHR监控代码 ... */ }
    ​// Fetch监控方法hookFetch() { /* ... 之前的Fetch监控代码 ... */ }
    ​// 资源监控方法monitorResources() { /* ... 之前的资源监控代码 ... */ }
    ​// 创建面板方法createPanel() { /* ... 之前的面板创建代码 ... */ }
    ​// 数据收集方法collectData(data) { /* ... 之前的数据收集代码 ... */ }
    ​// 数据上报方法report(data) { /* ... 之前的上报代码 ... */ }
    }

    使用时只需引入并实例化:

    import RequestStats from './request-stats.js';
    ​
    // 初始化工具
    new RequestStats({env: process.env.NODE_ENV, // 从环境变量获取当前环境threshold: 1500, // 自定义默认阈值reportUrl: '/api/performance/report'
    });

    8.2 按需加载

    在生产环境中,可以根据用户角色或 URL 参数决定是否启用工具,避免对普通用户造成影响:

    // 只对管理员或测试账号启用
    const userRole = getCurrentUserRole(); // 获取当前用户角色
    if (userRole === 'admin' || location.search.includes('debug=1')) {import('./request-stats.js').then(({ default: RequestStats }) => {new RequestStats({ env: 'production' });});
    }

    8.3 性能影响控制

    监控工具本身也可能影响页面性能,需要注意:

    • 避免在短时间内处理大量数据(如一次性加载 1000 + 请求)

    • 上报数据时使用防抖 / 节流,减少请求次数

    • 不在主线程做复杂计算(可使用 Web Worker 处理大数据分析)

    // 使用Web Worker处理数据计算
    // stats-worker.js
    self.onmessage = (e) => {const stats = calculateStats(e.data); // 复杂计算在Worker中进行self.postMessage(stats);
    };
    ​
    // 在主线程中使用
    const worker = new Worker('stats-worker.js');
    worker.postMessage(allRequests);
    worker.onmessage = (e) => {console.log('计算结果', e.data);updatePanelWithStats(e.data);
    };

    九、结语:让数据驱动性能优化

    "页面慢" 是前端开发中最常见也最棘手的问题之一。没有数据支撑时,优化就像盲人摸象 —— 你以为是图片太大,实际是接口响应慢;你以为是代码冗余,实际是 CDN 节点故障。

    本文设计的全站请求耗时统计工具,就像给开发者装上了 "性能显微镜":

    • 它能告诉你具体哪个请求慢(定位问题点)

    • 能告诉你慢了多久(量化问题严重程度)

    • 能告诉你什么时候慢(发现时间规律)

    • 能告诉你在什么环境下慢(排查环境因素)

    有了这些数据,性能优化就从 "凭感觉" 变成了 "按数据"—— 先通过工具找到性能瓶颈,再针对性优化,最后用工具验证优化效果。这才是科学的性能优化流程。

    最后送大家一句名言:"你无法改进你无法衡量的东西。"(You can't improve what you can't measure.)希望这个工具能帮助你衡量并改进你的网站性能,让 "用户说慢" 不再是难题。

    现在,不妨把这个工具加到你的项目中,看看你的网站到底哪里 "慢" 吧!

http://www.xdnf.cn/news/1407583.html

相关文章:

  • Vue3 响应式基础
  • 前端学习——JavaScript基础
  • 创维LB2004_安装软件教程
  • 37. 解数独
  • GaRe:面向非约束户外照片集的可重光照 3D 高斯溅射技术简要解析
  • Android开发-活动页面
  • C# .Net8 WinFormsApp使用日志Serilog组件
  • c++ Effective c++ 条款5
  • 机器学习之线性回归
  • 数据结构02:排序算法
  • PyQt5 进度条详细示例与性能优化
  • 电商系统的分布式事务调优
  • Knit-易用的prompt管理和调试工具
  • 第六章:透明度-Transparency《Unity Shaders and Effets Cookbook》
  • io进程线程;标准IO;0831
  • 【嵌入式】【调用函数图】手动绘制函数调用状态机
  • 【优先算法--前缀和】
  • 3DES加解密的算法Java Python Golang
  • CVPR上的多模态检索+视频理解,LLM助力提效翻倍
  • 8.1【Q】VMware相关
  • 吴恩达机器学习作业十一:异常检测
  • 大模型——利用RAG构建智能问答平台实战
  • 在Ubuntu服务器上安装KingbaseES V009R002C012(Orable兼容版)数据库过程详细记录
  • Qwen3_moe模型代码解析
  • FreeRTOS实战:任务创建与调度详解
  • 【MySQL自学】SQL语法全解(上篇)
  • 【PS实战】逐步打造静物的艺术色调(大学作业)
  • 从零开始搭建使用 TDengine:新用户快速上手指南
  • windows docker 中的mysql 无法被外部浏览器访问如何解决
  • 自动驾驶中的传感器技术37——Lidar(12)