“你说慢就慢?”——全站请求耗时统计工具设计全解析

“你说慢就慢?”——全站请求耗时统计工具设计全解析


前言:为什么“请求耗时统计”成了前端面试高频题?

在中高级前端面试中,有一个隐藏在“性能优化”、“前端监控”、“埋点系统”背后的常青问题,那就是:

“你有没有设计过请求耗时统计工具?你会怎么统计全站请求的耗时?”

这个问题频率之高,几乎可以和“你会不会节流防抖”一较高下。原因很简单:

  1. 可视化和可度量是性能优化的前提。如果你都不知道接口慢在哪儿,怎么去优化?
  2. 这题考察点很全:从前端架构理解、HTTP协议知识、XHR/Fetch 拦截、埋点策略,到最终的可视化呈现,面面俱到。
  3. 能体现候选人的系统设计能力,尤其是对请求链路和全局中间件体系的理解。

因此,这题不只是考察你“会不会写”,更重要的是你“能不能设计”和“是否理解底层机制”。


一、请求耗时统计:为什么重要?适用于哪些场景?

在日常开发中,我们经常遇到这些场景:

  • 某个页面数据加载很慢,不知道是哪个接口拖了后腿;
  • 用户频繁投诉页面卡顿,但后端表示一切正常;
  • 需要对接前端监控系统(如 Fundebug、Sentry、LogRocket)做接口耗时上报。

这时,我们就需要一套“全站请求耗时统计工具”来做三件事:

  1. 捕捉请求: 拦截所有页面发起的 HTTP 请求;
  2. 计算耗时: 记录请求的发出和响应时间,计算总耗时;
  3. 上报数据: 将统计结果发送到监控平台或者打印出来用于调试。

二、第一原理:请求拦截 —— 用统一入口监控一切请求

技术术语解释

我们常用的请求方式有两种:XMLHttpRequest 和 Fetch。要实现全站监控,我们要对它们进行“拦截”,也就是重写它们的行为

代码实现(Fetch为例)

const originalFetch = window.fetch;

window.fetch = asyncfunction (...args{
const start = Date.now();
try {
    const response = await originalFetch.apply(this, args);
    const end = Date.now();

    reportTiming(args[0], end - start); // 上报地址 & 耗时
    return response;
  } catch (err) {
    const end = Date.now();
    reportTiming(args[0], end - start, true);
    throw err;
  }
};

function reportTiming(url, duration, isError = false{
console.log(`[请求监控] ${url} - ${duration}ms ${isError ? '(失败)' : ''}`);
// TODO: 上报给服务端或监控平台
}

要点解释

  • apply(this, args) 保证原有 fetch 的上下文和参数不变;
  • 记录 start & end 时间,用来计算真实耗时;
  • 异常也要监控,失败的请求往往更值得注意。

这个方式最适合现代浏览器和新项目,兼容性好,逻辑清晰。对老项目则可能需要兼容 XMLHttpRequest


三、第二原理:XHR 拦截 —— 劫持原生 XMLHttpRequest

为什么还要拦截 XHR?

虽然 fetch 已普及,但不少老项目仍大量使用 axios 或 jQuery,这些默认基于 XMLHttpRequest 实现。拦截 XHR 是实现全面覆盖的关键。

核心代码

const originalXHR = window.XMLHttpRequest;

function wrapXHR({
const oldOpen = originalXHR.prototype.open;
const oldSend = originalXHR.prototype.send;

  originalXHR.prototype.open = function (method, url, async{
    this._url = url;
    oldOpen.apply(thisarguments);
  };

  originalXHR.prototype.send = function (...args{
    const start = Date.now();

    this.addEventListener('loadend', () => {
      const duration = Date.now() - start;
      reportTiming(this._url, duration, this.status >= 400);
    });

    oldSend.apply(this, args);
  };
}

wrapXHR();

关键点说明

  • 劫持 open 是为了保存请求地址;
  • 劫持 send 是为了注入 loadend 事件;
  • 事件中获取响应时间,适配所有状态(成功/失败);
  • 注意 this 指向,必须保持原型链。

四、第三原理:上报机制 —— 选择合适的方式发送监控数据

为什么不能用 fetch 上报监控?

因为如果你用 fetch 再去上报耗时信息,会再次触发监控逻辑,造成死循环。

推荐的上报方式:navigator.sendBeacon

function reportTiming(url, duration, isError = false{
  const data = {
    url,
    duration,
    status: isError ? 'error' : 'ok',
    timestampDate.now(),
  };

  const blob = new Blob([JSON.stringify(data)], { type'application/json' });
  navigator.sendBeacon('/monitor/collect', blob);
}

sendBeacon 的优点

  • 异步 & 非阻塞,不会阻碍主线程;
  • 不会触发再次请求劫持
  • 在页面卸载时仍能正常发送,非常适合收尾阶段上报。

如果你的项目在使用日志采集工具(如 Sentry、阿里云前端监控),可将 reportTiming 接入这些 SDK 的 track 接口中。


五、进阶扩展点:请求维度统计 + 视觉化呈现

如果你希望更进一步,可以考虑这些优化:

  • 按 URL 分组统计,分析哪个接口最慢;
  • 采样策略,比如只记录 10% 用户的数据;
  • 缓存近期请求耗时列表,在控制台可视化渲染请求排行;
  • 页面级平均耗时计算,可打通性能监控链路。

这些扩展都能显著提升系统监控粒度与可用性,属于高阶加分项。


总结:搭建一套通用的全站耗时统计方案,关键就三步

  1. 全站请求拦截(Fetch + XHR)
  2. 精确耗时计算(start + end)
  3. 安全稳定上报(sendBeacon 或日志平台)

这套系统可作为前端性能监控的基础设施,对日常调优和故障定位都非常有帮助。


面试秘籍:如何在面试中系统地回答“请求耗时统计”问题?

面试时不要直接说“我用了 axios 的拦截器”就完了,那太表层了。正确的答题思路应该是:

五步答题法:

  1. 开局描述场景:比如“我们遇到页面接口慢,但没有办法快速定位问题,于是我设计了一套全站请求耗时统计工具。”
  2. 讲清拦截原理:分 Fetch 和 XHR 两种方式,分别说清楚怎么劫持、怎么记录时间。
  3. 突出兼容性与鲁棒性:比如处理异常请求、如何避免循环调用等问题。
  4. 强调数据上报设计sendBeacon 的优势、上报节流策略、打点格式。
  5. 点出扩展方向:比如支持分组统计、采样策略、可视化面板等。

加分建议:

  • 如果你能画个简图或流程图,绝对锦上添花;
  • 能结合公司真实场景说“上线后某接口被发现耗时2.3秒,配合后端改造后优化了60%”,非常加分!

一点情绪价值送给你:

如果你认真理解并实践过这套方案,恭喜你已经具备了架构思维中的“全局拦截 + 数据打点 + 安全上报”三件套能力。这不只是一道题,而是一种工程师的职业素养。

保持学习、保持深度,面试问题就会变成你炫技的舞台。



最后

欢迎大家访问我们的刷题网站(https://fe.ecool.fun/)或者小程序 前端面试题宝典 进行刷题,1200多道全网最全的前端面试题,让你一网打尽。

有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。

图片