JS `Performance API` 深度:自定义性能指标与用户体验监控

大家好,今天咱们聊聊JS Performance API:打造你的用户体验监控神器!

各位观众老爷们,大家好!今天咱们不聊八卦,不谈风月,就来点实在的,聊聊如何用JS的 Performance API 打造属于你自己的用户体验监控系统。这玩意儿可不是什么高不可攀的黑科技,只要你稍微花点心思,就能让你的网站性能一览无余,用户体验蹭蹭往上涨。

咱们先来想想,啥叫用户体验?简单来说,就是用户在使用你的网站或者应用时的感觉。卡不卡?加载快不快?操作顺不顺手?这些都直接影响用户体验。而 Performance API 就是你的眼睛,帮你看到这些问题,让你能对症下药。

1. Performance API 是个啥?

Performance API 是一组 JavaScript 接口,它允许你访问有关当前页面性能的各种信息。它提供了测量和分析页面加载时间、资源加载时间、内存使用情况等等的能力。有了它,你就能像福尔摩斯一样,抽丝剥茧,找出性能瓶颈。

核心对象:window.performance

所有好戏都从 window.performance 这个对象开始。它就像一个百宝箱,里面装着各种性能数据。

一些常用的属性和方法:

  • performance.timing: 包含页面加载过程中各个阶段的时间戳信息。这个是老朋友了,但依然很有用。
  • performance.navigation: 提供有关用户如何导航到页面的信息(例如,是通过链接点击、刷新还是历史记录)。
  • performance.getEntries(): 返回一个 PerformanceEntry 对象的列表,这些对象记录了各种性能事件,比如资源加载、标记和度量。
  • performance.mark(): 允许你创建自定义的性能标记,用来测量特定代码段的执行时间。
  • performance.measure(): 允许你测量两个标记之间的持续时间。
  • performance.memory: 报告 JavaScript 堆内存使用情况(注意:这个API可能在某些浏览器中被弃用或限制使用)。

2. 摸清页面加载时间:performance.timing 的妙用

performance.timing 包含了一堆时间戳,记录了页面加载过程中发生的各种事件的时间。

举个例子:

const timing = performance.timing;

console.log("解析 DOM 树结构开始时间:", timing.domInteractive);
console.log("DOM 树结构解析完成时间:", timing.domComplete);
console.log("页面完全加载时间:", timing.loadEventEnd);

通过这些时间戳,你可以计算出:

  • DNS 查询时间: domainLookupEnd - domainLookupStart
  • TCP 连接时间: connectEnd - connectStart
  • 请求响应时间: responseEnd - requestStart
  • DOM 解析时间: domComplete - domInteractive
  • 页面完全加载时间: loadEventEnd - navigationStart

代码示例:计算页面加载时间

function calculatePageLoadTime() {
  const timing = performance.timing;

  const dnsLookupTime = timing.domainLookupEnd - timing.domainLookupStart;
  const tcpConnectTime = timing.connectEnd - timing.connectStart;
  const requestResponseTime = timing.responseEnd - timing.requestStart;
  const domParseTime = timing.domComplete - timing.domInteractive;
  const pageLoadTime = timing.loadEventEnd - timing.navigationStart;

  console.log("DNS 查询时间:", dnsLookupTime, "ms");
  console.log("TCP 连接时间:", tcpConnectTime, "ms");
  console.log("请求响应时间:", requestResponseTime, "ms");
  console.log("DOM 解析时间:", domParseTime, "ms");
  console.log("页面完全加载时间:", pageLoadTime, "ms");

  return {
    dnsLookupTime,
    tcpConnectTime,
    requestResponseTime,
    domParseTime,
    pageLoadTime,
  };
}

const loadTimes = calculatePageLoadTime();

注意事项:

  • performance.timing 已经被 PerformanceNavigationTiming 取代,但为了兼容性,仍然可以使用。
  • 时间戳的单位是毫秒。
  • 这些数据只是近似值,受到网络状况、浏览器性能等因素的影响。

3. 精确测量:performance.mark()performance.measure()

performance.mark() 允许你在代码中打上标记,performance.measure() 则可以测量两个标记之间的持续时间。这对于测量特定代码段的执行时间非常有用。

代码示例:测量函数执行时间

function myExpensiveFunction() {
  // 模拟一个耗时的操作
  let sum = 0;
  for (let i = 0; i < 1000000; i++) {
    sum += i;
  }
  return sum;
}

function measureFunctionExecutionTime(func) {
  performance.mark("functionStart");
  func();
  performance.mark("functionEnd");

  performance.measure("functionExecution", "functionStart", "functionEnd");

  const measurements = performance.getEntriesByName("functionExecution");
  const duration = measurements[0].duration;

  console.log(`函数 ${func.name} 执行时间:`, duration, "ms");

  performance.clearMarks("functionStart");
  performance.clearMarks("functionEnd");
  performance.clearMeasures("functionExecution");

  return duration;
}

const executionTime = measureFunctionExecutionTime(myExpensiveFunction);

解释一下:

  1. performance.mark("functionStart"): 在函数执行前打上一个标记。
  2. func(): 执行你要测量的函数。
  3. performance.mark("functionEnd"): 在函数执行后打上另一个标记。
  4. performance.measure("functionExecution", "functionStart", "functionEnd"): 测量 functionStartfunctionEnd 之间的持续时间,并将其命名为 "functionExecution"。
  5. performance.getEntriesByName("functionExecution"): 获取所有名为 "functionExecution" 的性能条目。
  6. measurements[0].duration: 获取第一个性能条目的持续时间。
  7. performance.clearMarks()performance.clearMeasures(): 清理标记和测量结果,避免干扰后续的测量。

应用场景:

  • 测量组件渲染时间。
  • 测量 API 请求时间。
  • 测量复杂算法的执行时间。
  • 诊断性能瓶颈。

4. 追踪资源加载:performance.getEntriesByType()

performance.getEntriesByType() 允许你获取特定类型的性能条目,比如 "resource"。这对于追踪资源加载时间非常有用。

代码示例:获取所有图片加载时间

function getImagesLoadTimes() {
  const resources = performance.getEntriesByType("resource");

  const imageLoadTimes = resources
    .filter((resource) => resource.initiatorType === "img")
    .map((resource) => ({
      name: resource.name,
      duration: resource.duration,
    }));

  console.table(imageLoadTimes);

  return imageLoadTimes;
}

const images = getImagesLoadTimes();

解释一下:

  1. performance.getEntriesByType("resource"): 获取所有资源类型的性能条目。
  2. resource.initiatorType === "img": 过滤出 initiatorType 为 "img" 的资源,也就是图片。
  3. resource.name: 图片的 URL。
  4. resource.duration: 图片加载时间。
  5. console.table(imageLoadTimes): 以表格的形式打印图片加载时间。

应用场景:

  • 找出加载时间过长的图片或脚本。
  • 优化资源加载顺序。
  • 监控第三方资源的性能。

5. 监控用户体验:自定义性能指标

除了使用 Performance API 提供的标准指标外,你还可以创建自定义的性能指标来更好地监控用户体验。

一些常用的自定义指标:

  • 首次内容绘制 (First Contentful Paint, FCP): 浏览器首次绘制任何文本、图像、非白色画布或 SVG 的时间点。
  • 最大内容绘制 (Largest Contentful Paint, LCP): 视口中最大的可见元素完成渲染的时间点。
  • 首次输入延迟 (First Input Delay, FID): 用户首次与页面交互(例如,点击链接、按钮或使用自定义的 JavaScript 驱动的控件)到浏览器实际能够响应交互之间的时间。
  • Cumulative Layout Shift (CLS): 测量页面上非预期布局移动的视觉稳定性。

代码示例:测量 FCP

function getFCP() {
  return new Promise((resolve) => {
    new PerformanceObserver((entryList) => {
      const paintEntries = entryList.getEntriesByName("first-contentful-paint");
      if (paintEntries.length > 0) {
        const fcp = paintEntries[0].startTime;
        resolve(fcp);
      }
    }).observe({ type: "paint", buffered: true });
  });
}

getFCP().then((fcp) => {
  console.log("FCP:", fcp, "ms");
});

代码示例:测量 LCP

function getLCP() {
  return new Promise((resolve) => {
    new PerformanceObserver((entryList) => {
      const entries = entryList.getEntries();
      const lastEntry = entries[entries.length - 1];
      resolve(lastEntry.startTime);
    }).observe({ type: "largest-contentful-paint", buffered: true });
  });
}

getLCP().then((lcp) => {
  console.log("LCP:", lcp, "ms");
});

代码示例:测量 FID

function getFID() {
  return new Promise((resolve) => {
    new PerformanceObserver((entryList) => {
      const entries = entryList.getEntries();
      const fid = entries[0].processingStart - entries[0].startTime;
      resolve(fid);
    }).observe({ type: "first-input", buffered: true });
  });
}

getFID().then((fid) => {
  console.log("FID:", fid, "ms");
});

解释一下:

  • PerformanceObserver: 监听特定类型的性能事件。
  • entryList.getEntries(): 获取所有性能条目。
  • entryList.getEntriesByName(): 获取指定名称的性能条目。
  • startTime: 事件发生的时间。
  • duration: 事件的持续时间。

注意事项:

  • 这些指标需要使用 PerformanceObserver 来监听,因为它们是在页面加载过程中动态变化的。
  • 这些指标的计算方法可能会因浏览器而异。

6. 数据上报:将性能数据发送到服务器

有了性能数据,下一步就是将它们发送到服务器,以便进行分析和监控。

一些常用的数据上报方式:

  • XMLHttpRequest (XHR): 传统的 Ajax 请求。
  • fetch API: 更现代的 Ajax 请求。
  • navigator.sendBeacon(): 专门用于发送分析数据的 API,即使页面即将卸载也能可靠地发送数据。

代码示例:使用 navigator.sendBeacon() 上报性能数据

function reportPerformanceData(data) {
  const url = "/api/performance"; // 你的服务器端点

  const blob = new Blob([JSON.stringify(data)], {
    type: "application/json",
  });

  navigator.sendBeacon(url, blob);
}

window.addEventListener("load", () => {
  const performanceData = {
    pageLoadTime: calculatePageLoadTime(),
    fcp: getFCP(),
    lcp: getLCP(),
    fid: getFID(),
  };

  Promise.all([
    performanceData.fcp,
    performanceData.lcp,
    performanceData.fid,
  ]).then((values) => {
    performanceData.fcp = values[0];
    performanceData.lcp = values[1];
    performanceData.fid = values[2];
    reportPerformanceData(performanceData);
  });
});

解释一下:

  1. reportPerformanceData(data): 将性能数据发送到服务器。
  2. url: 你的服务器端点。
  3. blob: 将数据转换为 Blob 对象,以便发送。
  4. navigator.sendBeacon(url, blob): 使用 navigator.sendBeacon() 发送数据。
  5. window.addEventListener("load", ...): 在页面加载完成后执行代码。
  6. calculatePageLoadTime(), getFCP(), getLCP(), getFID(): 获取性能数据。
  7. Promise.all(...): 等待所有异步操作完成。
  8. reportPerformanceData(performanceData): 将性能数据发送到服务器。

注意事项:

  • navigator.sendBeacon() 是异步的,不会阻塞页面卸载。
  • 服务器端需要接收和处理这些数据。

7. 总结:打造你的用户体验监控系统

通过 Performance API,你可以:

  • 监控页面加载时间。
  • 测量特定代码段的执行时间。
  • 追踪资源加载时间。
  • 创建自定义性能指标。
  • 将性能数据发送到服务器。

有了这些工具,你就可以打造一个属于你自己的用户体验监控系统,让你的网站性能一览无余,用户体验蹭蹭往上涨!

性能指标参考:

指标 描述 目标值 (越低越好)
First Contentful Paint (FCP) 浏览器首次渲染任何内容的时间 1.8 秒以内
Largest Contentful Paint (LCP) 视口中最大的内容元素完成渲染的时间 2.5 秒以内
First Input Delay (FID) 用户首次与页面交互到浏览器响应之间的时间,衡量交互性 100 毫秒以内
Cumulative Layout Shift (CLS) 页面视觉稳定性指标,衡量页面布局的意外偏移 0.1 以内
Time to First Byte (TTFB) 浏览器接收到服务器响应的第一个字节的时间,衡量服务器响应速度 0.8 秒以内
Total Blocking Time (TBT) 页面加载过程中,阻止用户交互的总时间,是 FID 的一个替代指标(在实验室中) 300 毫秒以内

一些建议:

  • 定期监控你的网站性能。
  • 设置性能预算,以便及时发现问题。
  • 使用自动化工具来收集和分析性能数据。
  • 根据性能数据来优化你的网站。

最后,希望今天的分享对大家有所帮助。记住,用户体验至上! 咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注