在浏览器环境下使用 TypeScript 操作 Performance API 的主流实践

在前端应用日益复杂的今天,性能监控已成为提升用户体验的关键环节。结合 TypeScript 的类型安全特性和浏览器原生 Performance API,我们可以构建出既健壮又高效的性能监控系统。本文将深入探讨主流的使用案例和最佳实践。

一、Performance API 与 TypeScript 基础

1.1 API 概览

Performance API 提供了高精度的时间测量能力,核心接口包括:

  • performance.now():微秒级时间戳

  • PerformanceObserver:异步监听性能事件

  • PerformanceNavigationTiming:页面导航耗时

  • PerformanceResourceTiming:资源加载详情

1.2 TypeScript 类型定义

首先定义基础数据结构和枚举类型,确保类型安全:

// 监控数据类型定义
export enum TraceTypeEnum {
  performance = 'performance',
  error = 'error'
}

export enum TraceSubTypeEnum {
  resource = 'resource',
  fcp = 'fcp',
  lcp = 'lcp',
  load = 'load'
}

export interface PerformanceMetric {
  type: TraceTypeEnum.performance;
  subType: TraceSubTypeEnum;
  pageUrl: string;
  timestamp: number;
  [key: string]: any;
}

export interface ResourceTimingData extends PerformanceMetric {
  name: string;
  sourceType: string;
  duration: number;
  dns: number;
  tcp: number;
  ttfb: number;
  responseBodySize: number;
}

二、主流实践案例

2.1 资源加载性能监控

使用 PerformanceObserver 捕获所有资源加载详情,这是现代浏览器推荐的方式:

export function initResourceTimingMonitor(reportUrl: string): void {
  const host = new URL(reportUrl).host;
  
  const entryHandler = (list: PerformanceObserverEntryList) => {
    const entries = list.getEntries() as PerformanceResourceTiming[];
    
    entries.forEach(entry => {
      // 避免监控 SDK 自身请求导致的循环上报
      if (entry.name.includes(host)) return;

      const timingData: ResourceTimingData = {
        type: TraceTypeEnum.performance,
        subType: TraceSubTypeEnum.resource,
        name: entry.name,
        sourceType: entry.initiatorType,
        duration: entry.duration,
        dns: entry.domainLookupEnd - entry.domainLookupStart,
        tcp: entry.connectEnd - entry.connectStart,
        redirect: entry.redirectEnd - entry.redirectStart,
        ttfb: entry.responseStart - entry.requestStart,
        protocol: entry.nextHopProtocol,
        responseBodySize: entry.encodedBodySize,
        responseHeaderSize: entry.transferSize - entry.encodedBodySize,
        transferSize: entry.transferSize,
        resourceSize: entry.decodedBodySize,
        startTime: entry.startTime,
        pageUrl: window.location.href,
        timestamp: Date.now()
      };

      // 批量上报
      batchReport(timingData);
    });
  };

  const observer = new PerformanceObserver(entryHandler);
  observer.observe({ type: 'resource', buffered: true });
}

2.2 Web Vitals 核心指标监控

Google 的 Web Vitals 是评估用户体验的关键指标,包括 FCP、LCP、FID 等:

// 首次内容绘制 (FCP)
export function monitorFCP(): void {
  const entryHandler = (list: PerformanceObserverEntryList) => {
    for (const entry of list.getEntries()) {
      if (entry.name === 'first-contentful-paint') {
        const metricData: PerformanceMetric = {
          type: TraceTypeEnum.performance,
          subType: TraceSubTypeEnum.fcp,
          value: entry.startTime,
          pageUrl: window.location.href,
          timestamp: Date.now()
        };
        
        batchReport(metricData);
        observer.disconnect();
      }
    }
  };

  const observer = new PerformanceObserver(entryHandler);
  observer.observe({ type: 'paint', buffered: true });
}

// 最大内容绘制 (LCP)
export function monitorLCP(): void {
  let maxLCPValue = 0;
  
  const entryHandler = (list: PerformanceObserverEntryList) => {
    const entries = list.getEntries() as PerformanceEntry[];
    
    for (const entry of entries) {
      const { startTime, size, element } = entry as any;
      if (startTime > maxLCPValue) {
        maxLCPValue = startTime;
        
        const lcpData: PerformanceMetric = {
          type: TraceTypeEnum.performance,
          subType: TraceSubTypeEnum.lcp,
          value: startTime,
          element: element?.tagName || '',
          size,
          pageUrl: window.location.href,
          timestamp: Date.now()
        };
        
        batchReport(lcpData);
      }
    }
  };

  const observer = new PerformanceObserver(entryHandler);
  observer.observe({ type: 'largest-contentful-paint', buffered: true });
}

2.3 自定义性能打点

在业务关键路径中插入自定义标记,精确测量特定操作耗时:

class PerformanceTracker {
  private marks = new Map<string, number>();

  // 开始标记
  start(markName: string): void {
    performance.mark(`start:${markName}`);
    this.marks.set(markName, performance.now());
  }

  // 结束标记并计算耗时
  end(markName: string): number {
    const startTime = this.marks.get(markName);
    if (!startTime) {
      throw new Error(`Performance mark '${markName}' not found`);
    }

    const endTime = performance.now();
    const duration = endTime - startTime;
    
    performance.measure(markName, `start:${markName}`, `end:${markName}`);
    
    // 上报自定义指标
    const customMetric: PerformanceMetric = {
      type: TraceTypeEnum.performance,
      subType: 'custom' as any,
      name: markName,
      duration,
      pageUrl: window.location.href,
      timestamp: Date.now()
    };
    
    batchReport(customMetric);
    
    return duration;
  }

  // 测量异步函数
  async measureAsync<T>(name: string, fn: () => Promise<T>): Promise<T> {
    this.start(name);
    try {
      const result = await fn();
      return result;
    } finally {
      this.end(name);
    }
  }
}

// 使用示例
const tracker = new PerformanceTracker();

async function fetchUserData(): Promise<UserData> {
  return tracker.measureAsync('fetchUserData', async () => {
    const response = await fetch('/api/user');
    return response.json();
  });
}

2.4 页面加载完整耗时分析

综合使用 Navigation Timing API 分析页面加载全链路:

export function monitorNavigationTiming(): void {
  // 优先使用 PerformanceObserver (Level 2)
  try {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries() as PerformanceNavigationTiming[]) {
        if (entry.type === 'navigation') {
          const timingData = {
            type: TraceTypeEnum.performance,
            subType: 'navigation',
            dnsTime: entry.domainLookupEnd - entry.domainLookupStart,
            tcpTime: entry.connectEnd - entry.connectStart,
            ttfb: entry.responseStart - entry.requestStart,
            responseTime: entry.responseEnd - entry.responseStart,
            domParseTime: entry.domContentLoadedEventStart - entry.responseEnd,
            resourceLoadTime: entry.loadEventStart - entry.domContentLoadedEventEnd,
            totalLoadTime: entry.loadEventEnd - entry.fetchStart,
            redirectCount: entry.redirectCount,
            pageUrl: window.location.href,
            timestamp: Date.now()
          };
          
          batchReport(timingData);
        }
      }
    });
    
    observer.observe({ type: 'navigation', buffered: true });
  } catch {
    // 降级方案:使用 performance.timing (Level 1)
    window.addEventListener('load', () => {
      setTimeout(() => { // 确保 loadEventEnd 已记录
        const timing = performance.timing;
        const navigationData = {
          type: TraceTypeEnum.performance,
          subType: 'navigation',
          dnsTime: timing.domainLookupEnd - timing.domainLookupStart,
          tcpTime: timing.connectEnd - timing.connectStart,
          ttfb: timing.responseStart - timing.requestStart,
          totalLoadTime: timing.loadEventEnd - timing.fetchStart,
          pageUrl: window.location.href,
          timestamp: Date.now()
        };
        
        batchReport(navigationData);
      }, 100);
    });
  }
}

2.5 批量上报策略

实现高效的数据上报机制,减少网络请求:

class MonitorSDK {
  private queue: any[] = [];
  private timeoutId: NodeJS.Timeout | null = null;
  private readonly config = {
    maxBatchSize: 10,
    maxWaitTime: 5000,
    reportUrl: '/api/performance'
  };

  addToQueue(data: any): void {
    this.queue.push(data);
    
    // 达到批量大小立即上报
    if (this.queue.length >= this.config.maxBatchSize) {
      this.flushQueue();
    } else if (!this.timeoutId) {
      // 否则延迟上报
      this.timeoutId = setTimeout(() => {
        this.flushQueue();
      }, this.config.maxWaitTime);
    }
  }

  private flushQueue(): void {
    if (this.queue.length === 0) return;
    
    const batchData = [...this.queue];
    this.queue = [];
    
    if (this.timeoutId) {
      clearTimeout(this.timeoutId);
      this.timeoutId = null;
    }

    // 使用 navigator.sendBeacon 确保页面卸载时也能上报
    if (navigator.sendBeacon) {
      navigator.sendBeacon(
        this.config.reportUrl,
        JSON.stringify({ batch: batchData })
      );
    } else {
      fetch(this.config.reportUrl, {
        method: 'POST',
        body: JSON.stringify({ batch: batchData }),
        headers: { 'Content-Type': 'application/json' },
        keepalive: true
      }).catch(console.error);
    }
  }
}

三、高级应用技巧

3.1 Service Worker 性能监控

结合 Service Worker 实现离线缓存和性能优化:

// 在 Service Worker 中监控缓存性能
self.addEventListener('fetch', (event: FetchEvent) => {
  const startTime = performance.now();
  
  event.respondWith(
    caches.match(event.request).then(cachedResponse => {
      if (cachedResponse) {
        // 从缓存读取
        const duration = performance.now() - startTime;
        reportToAnalytics({
          type: 'cache-hit',
          url: event.request.url,
          duration
        });
        return cachedResponse;
      }
      
      // 网络请求并缓存
      return fetch(event.request).then(response => {
        const duration = performance.now() - startTime;
        reportToAnalytics({
          type: 'network-fetch',
          url: event.request.url,
          duration
        });
        
        const clone = response.clone();
        caches.open('v1').then(cache => cache.put(event.request, clone));
        return response;
      });
    })
  );
});

3.2 Web Workers 性能隔离

将耗时操作放到 Web Worker 中,避免阻塞主线程:

// main.ts
const worker = new Worker('./performance-worker.ts');

worker.postMessage({ type: 'start-heavy-task' });

worker.onmessage = (event: MessageEvent) => {
  const { type, duration, data } = event.data;
  
  if (type === 'task-complete') {
    console.log(`任务耗时: ${duration}ms`);
    // 上报性能数据
    batchReport({
      type: TraceTypeEnum.performance,
      subType: 'worker-task',
      duration,
      timestamp: Date.now()
    });
  }
};

// performance-worker.ts
self.addEventListener('message', (event) => {
  if (event.data.type === 'start-heavy-task') {
    const start = performance.now();
    
    // 执行耗时计算
    const result = heavyComputation();
    
    const duration = performance.now() - start;
    
    self.postMessage({
      type: 'task-complete',
      duration,
      data: result
    });
  }
});

3.3 3D 游戏性能监控案例

参考 Three.js 游戏项目的性能优化实践:

class GamePerformanceMonitor {
  private frameCount = 0;
  private lastFpsUpdate = performance.now();
  private fps = 0;

  monitorRenderLoop(renderer: THREE.WebGLRenderer): void {
    const monitor = () => {
      this.frameCount++;
      const now = performance.now();
      
      // 每秒计算一次 FPS
      if (now >= this.lastFpsUpdate + 1000) {
        this.fps = Math.round(
          (this.frameCount * 1000) / (now - this.lastFpsUpdate)
        );
        
        if (this.fps < 30) {
          // 性能警告
          console.warn(`低 FPS 警告: ${this.fps}`);
        }
        
        // 上报性能指标
        batchReport({
          type: TraceTypeEnum.performance,
          subType: 'fps',
          value: this.fps,
          timestamp: Date.now()
        });
        
        this.frameCount = 0;
        this.lastFpsUpdate = now;
      }
      
      requestAnimationFrame(monitor);
    };
    
    monitor();
  }

  // 监控特效渲染性能
  monitorEffect(effectName: string, renderFn: () => void): void {
    const start = performance.now();
    renderFn();
    const duration = performance.now() - start;
    
    if (duration > 16.67) { // 超过一帧时间(60fps)
      console.warn(`特效 ${effectName} 渲染耗时: ${duration.toFixed(2)}ms`);
    }
  }
}

四、性能优化最佳实践

4.1 避免监控本身影响性能

  1. 采样率控制:只收集部分用户的数据

const shouldMonitor = Math.random() < 0.1; // 10% 采样
if (!shouldMonitor) return;
  1. 防抖与节流:对频繁触发的事件进行限流

import { debounce } from 'lodash';

const debouncedReport = debounce(batchReport, 1000, { 
  maxWait: 5000 
});
  1. 非阻塞上报:使用 requestIdleCallback 延迟上报

if ('requestIdleCallback' in window) {
  requestIdleCallback(() => flushQueue());
} else {
  setTimeout(() => flushQueue(), 0);
}

4.2 类型守卫确保运行时安全

function isPerformanceEntry(entry: any): entry is PerformanceEntry {
  return entry && 
    typeof entry.name === 'string' && 
    typeof entry.startTime === 'number';
}

function processEntry(entry: unknown): void {
  if (!isPerformanceEntry(entry)) {
    console.warn('Invalid performance entry');
    return;
  }
  // TypeScript 现在知道 entry 是 PerformanceEntry 类型
  console.log(entry.startTime);
}

4.3 常量枚举优化性能

使用 const enum 减少编译后代码体积:

const enum MetricType {
  FCP = 'first-contentful-paint',
  LCP = 'largest-contentful-paint',
  LOAD = 'load'
}

// 编译后直接使用字符串字面量,无运行时开销
if (entry.name === MetricType.FCP) {
  // ...
}

五、实战:完整监控 SDK 集成

5.1 初始化配置

interface MonitorConfig {
  appId: string;
  appVersion: string;
  reportUrl: string;
  sampleRate?: number;
  enableWebVitals?: boolean;
  enableResourceTiming?: boolean;
}

class PerformanceMonitorSDK {
  private config: Required<MonitorConfig>;
  private initialized = false;

  constructor(options: MonitorConfig) {
    this.config = {
      sampleRate: 1,
      enableWebVitals: true,
      enableResourceTiming: true,
      ...options
    };
  }

  init(): void {
    if (this.initialized) {
      console.warn('Monitor SDK already initialized');
      return;
    }

    // 采样控制
    if (Math.random() > this.config.sampleRate) {
      console.log('Performance monitoring skipped due to sample rate');
      return;
    }

    // 初始化各项监控
    this.config.enableWebVitals && this.initWebVitals();
    this.config.enableResourceTiming && initResourceTimingMonitor(this.config.reportUrl);
    
    // 页面卸载时确保数据上报
    window.addEventListener('beforeunload', () => {
      this.flushQueue();
    });

    this.initialized = true;
    console.log('Performance Monitor SDK initialized');
  }
}

5.2 实际应用案例

// 在项目入口文件中初始化
const monitor = new PerformanceMonitorSDK({
  appId: 'my-web-app',
  appVersion: '2.1.0',
  reportUrl: 'https://analytics.example.com/api/performance',
  sampleRate: 0.5, // 50% 用户采样
  enableWebVitals: true,
  enableResourceTiming: true
});

monitor.init();

// 业务代码中打点
const tracker = new PerformanceTracker();

async function bootApplication() {
  await tracker.measureAsync('app-bootstrap', async () => {
    // 初始化路由
    // 加载用户配置
    // 渲染根组件
  });
}

六、总结与展望

通过 TypeScript 与 Performance API 的结合,我们可以:

  1. 构建类型安全的性能监控系统,减少运行时错误

  2. 精确测量业务关键路径,定位性能瓶颈

  3. 实现自动化数据上报,持续监控应用健康状况

  4. 优化用户体验,基于真实数据驱动改进

未来发展趋势:

  • 与 OpenTelemetry 集成,实现前后端全链路追踪

  • AI 驱动的性能分析,自动识别异常模式

  • RUM (Real User Monitoring) 与 Synthetic Monitoring 的深度结合

已经博主授权,源码转载自 https://pan.quark.cn/s/a4b39357ea24 QueueForMcu 基于单片机实现的队列功能模块,主要用于8位、16位、32位非运行RTOS的单片机应用,兼容大多数单片机平台。 开源代码:https://.com/xiaoxinpro/QueueForMcu 一、特性 动态创建队列对象 动态设置队列数据缓冲区 静态指定队列元素数据长度 采用值传递的方式保存队列数据 二、快速使用 三、配置说明 目前QueueForMcu只有一个静态配置项,具体如下: 在文件 中有一个宏定义 用于指定队列元素的数据长度,默认是 ,可以根据需要更改为其他数据类型。 四、数据结构 队列的数据结构为 用于保存队列的状态,源码如下: 其中 为配置项中自定义的数据类型。 五、创建队列 1、创建队列缓存 由于我们采用值传递的方式保存队列数据,因此我们在创建队列前要手动创建一个队列缓存区,用于存放队列数据。 以上代码即创建一个大小为 的队列缓存区。 2、创建队列结构 接下来使用 创建队列结构,用于保存队列的状态: 3、初始化队列 准备好队列缓存和队列结构后调用 函数来创建队列,该函数原型如下: 参数说明: 参考代码: 六、压入队列 1、单数据压入 将数据压入队列尾部使用 函数,该函数原型如下: 参数说明: 返回值说明: 该函数会返回一个 枚举数据类型,返回值会根据队列状态返回以下几个值: 参考代码: 2、多数据压入 若需要将多个数据(数组)压入队列可以使用 函数,原理上循环调用 函数来实现的,函数原型如下: 参数说明: 当数组长度大于队列剩余长度时,数组多余的数据将被忽略。 返回值说明: 该函数将返回实际被压入到队列中的数据长度。 当队列中的剩余长度富余...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值