在 Vue 3 的响应式系统与 Composition API 加持下,结合 TypeScript 严格模式的类型安全特性,我们可以构建出既精确又健壮的浏览器性能监控体系。本文将深入探讨在 Vue 3 生态中使用performance.now()的典型场景与最佳实践。
一、严格模式与 Vue 3 的完美结合
1.1 TypeScript 配置
{ "compilerOptions": { "strict": true, "strictNullChecks": true, "noImplicitAny": true, "strictFunctionTypes": true } }1.2 Vue 3 类型增强
// shims-vue.d.ts declare module 'vue-router' { interface RouteMeta { // 为路由元信息添加性能监控配置 perfMark?: string; enableTrack?: boolean; } } // performance.d.ts interface Window { performance: Performance & { // 扩展类型定义以支持新 API measureUserAgentSpecificMemory?: () => Promise<any>; }; }场景一:组件生命周期性能追踪
2.1 通用性能监控 Hook
import { onMounted, onUpdated, onUnmounted, ref } from 'vue'; interface PerformanceMetrics { mountTime: number | null; updateTime: number | null; unmountTime: number | null; } /** * 严格模式:组件生命周期耗时监控 Composable * @param componentName 组件名称 * @returns 响应式性能指标 */ export function useComponentPerformance(componentName: string) { const metrics = ref<PerformanceMetrics>({ mountTime: null, updateTime: null, unmountTime: null }); // 类型守卫:确保性能 API 可用 const isPerformanceSupported = (): boolean => { return typeof performance !== 'undefined' && typeof performance.now === 'function' && typeof performance.mark === 'function'; }; // 严格模式:处理可能为 null 的情况 const safeNow = (): number => { return isPerformanceSupported() ? performance.now() : Date.now(); }; onMounted(() => { const startTime = safeNow(); // 在下一个微任务中测量,确保 DOM 渲染完成 Promise.resolve().then(() => { metrics.value.mountTime = safeNow() - startTime; if (metrics.value.mountTime > 100) { console.warn(`[${componentName}] 挂载耗时超过100ms: ${metrics.value.mountTime.toFixed(2)}ms`); } // 上报监控系统 reportToAnalytics({ type: 'vue-lifecycle', component: componentName, phase: 'mount', duration: metrics.value.mountTime, timestamp: safeNow() }); }); }); let updateStartTime: number | null = null; onUpdated(() => { if (updateStartTime === null) { updateStartTime = safeNow(); } // 防抖:避免频繁更新 Promise.resolve().then(() => { if (updateStartTime !== null) { metrics.value.updateTime = safeNow() - updateStartTime; updateStartTime = null; if (metrics.value.updateTime > 50) { console.warn(`[${componentName}] 更新耗时超过50ms: ${metrics.value.updateTime.toFixed(2)}ms`); } } }); }); onUnmounted(() => { const startTime = safeNow(); // Vue 3 中 onUnmounted 同步执行 metrics.value.unmountTime = safeNow() - startTime; // 清理性能标记 if (isPerformanceSupported()) { performance.clearMarks(); performance.clearMeasures(); } }); return { metrics }; } // 组件中使用 <script setup lang="ts"> import { ref } from 'vue'; import { useComponentPerformance } from '@/composables/usePerformance'; const props = defineProps<{ userId: string }>(); const userData = ref(null); // 启用性能监控 const { metrics } = useComponentPerformance('UserProfile'); // 模拟数据加载 const loadUser = async () => { const start = performance.now(); const response = await fetch(`/api/users/${props.userId}`); userData.value = await response.json(); console.log(`数据加载耗时: ${performance.now() - start}ms`); }; </script>场景二:异步操作与请求性能追踪
3.1 基于 Pinia 的全局请求性能监控
// stores/performance.ts import { defineStore } from 'pinia'; import type { Router } from 'vue-router'; export interface RequestMetrics { url: string; method: string; duration: number; status: number; timestamp: number; phase: 'dns' | 'tcp' | 'ttfb' | 'download'; } export const usePerformanceStore = defineStore('performance', () => { const requestMetrics = ref<RequestMetrics[]>([]); const slowRequests = computed(() => requestMetrics.value.filter(r => r.duration > 1000) ); /** * 严格模式:添加请求指标 */ function addRequestMetric(metric: RequestMetrics): void { requestMetrics.value.push(metric); // 限制数组大小,避免内存泄漏 if (requestMetrics.value.length > 100) { requestMetrics.value.shift(); } } // 与 Vue Router 集成,自动追踪路由切换 function trackRouteChanges(router: Router): void { let startTime: number | null = null; router.beforeEach((to, from, next) => { // 严格模式:检查是否启用性能追踪 if (to.meta.enableTrack !== false) { startTime = performance.now(); } next(); }); router.afterEach((to) => { if (startTime !== null && to.meta.enableTrack !== false) { const duration = performance.now() - startTime; addRequestMetric({ url: to.fullPath, method: 'ROUTE_CHANGE', duration, status: 200, timestamp: performance.now(), phase: 'download' }); // 慢路由警告 if (duration > 500) { console.warn(`[Slow Route] ${to.path}: ${duration.toFixed(2)}ms`); } startTime = null; } }); } return { requestMetrics, slowRequests, addRequestMetric, trackRouteChanges }; }); // 在 main.ts 中初始化 const performanceStore = usePerformanceStore(router); performanceStore.trackRouteChanges(router);3.2 请求拦截器集成
// utils/http-client.ts import axios, { type AxiosInstance } from 'axios'; import { usePerformanceStore } from '@/stores/performance'; export function createHttpClient(): AxiosInstance { const client = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000 }); // 严格模式:为 config 添加类型 client.interceptors.request.use((config) => { // 附加时间戳到 config (config as any).__startTime = performance.now(); return config; }); client.interceptors.response.use( (response) => { const duration = performance.now() - (response.config as any).__startTime; // 严格模式:确保 store 已初始化 const performanceStore = usePerformanceStore(); performanceStore.addRequestMetric({ url: response.config.url || '', method: response.config.method?.toUpperCase() || 'GET', duration, status: response.status, timestamp: performance.now(), phase: 'download' }); return response; }, (error) => { const duration = performance.now() - (error.config as any).__startTime; const performanceStore = usePerformanceStore(); performanceStore.addRequestMetric({ url: error.config?.url || '', method: error.config?.method?.toUpperCase() || 'GET', duration, status: error.response?.status || 0, timestamp: performance.now(), phase: 'download' }); return Promise.reject(error); } ); return client; }场景三:动画与交互性能监控
4.1 基于 performance.now() 的 FPS 监控
// composables/useFPSMonitor.ts import { ref, onUnmounted, type Ref } from 'vue'; interface FPSMetrics { current: number; min: number; max: number; average: number; frameCount: number; } export function useFPSMonitor(): Ref<FPSMetrics> { const metrics = ref<FPSMetrics>({ current: 0, min: 144, max: 0, average: 0, frameCount: 0 }); let isRunning = false; let lastTime = 0; let frameCount = 0; let rafId: number | null = null; const fpsHistory: number[] = []; const isPerformanceSupported = (): boolean => { return typeof performance !== 'undefined' && typeof performance.now === 'function'; }; const tick = (currentTime: number) => { if (!isRunning) return; frameCount++; // 严格模式:必须检查 lastTime 是否有效 if (lastTime > 0) { const deltaTime = currentTime - lastTime; const fps = Math.round(1000 / deltaTime); // 更新统计 fpsHistory.push(fps); if (fpsHistory.length > 60) { fpsHistory.shift(); } // 严格模式:处理空数组 const average = fpsHistory.length > 0 ? fpsHistory.reduce((a, b) => a + b, 0) / fpsHistory.length : 0; metrics.value = { current: fps, min: Math.min(...fpsHistory, metrics.value.min), max: Math.max(...fpsHistory, metrics.value.max), average: Math.round(average), frameCount }; // 性能警告 if (fps < 30) { console.warn(`[FPS Warning] 帧率过低: ${fps} FPS`); } } lastTime = currentTime; rafId = requestAnimationFrame(tick); }; const start = () => { if (!isPerformanceSupported()) { console.warn('Performance API not available'); return; } isRunning = true; lastTime = performance.now(); rafId = requestAnimationFrame(tick); }; const stop = () => { isRunning = false; if (rafId !== null) { cancelAnimationFrame(rafId); } }; // 自动启动 start(); // 组件卸载时清理 onUnmounted(() => { stop(); }); return metrics; } // 在组件中使用 <template> <div class="fps-counter" v-if="showFPS"> FPS: {{ fpsMetrics.current }} (Avg: {{ fpsMetrics.average }}) </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import { useFPSMonitor } from '@/composables/useFPSMonitor'; const showFPS = ref(import.meta.env.DEV); // 仅在开发环境显示 const fpsMetrics = useFPSMonitor(); </script>4.2 滚动性能监控
// composables/useScrollPerformance.ts import { ref, onMounted, onUnmounted } from 'vue'; export interface ScrollMetrics { scrollCount: number; averageDelay: number; maxDelay: number; isJanky: boolean; } export function useScrollPerformance(element?: HTMLElement) { const metrics = ref<ScrollMetrics>({ scrollCount: 0, averageDelay: 0, maxDelay: 0, isJanky: false }); let lastScrollTime = 0; let delays: number[] = []; let rafId: number | null = null; const isPerformanceSupported = () => typeof performance !== 'undefined' && typeof performance.now === 'function'; const handleScroll = () => { if (!isPerformanceSupported()) return; const currentTime = performance.now(); // 使用 requestAnimationFrame 测量延迟 if (rafId !== null) { cancelAnimationFrame(rafId); } rafId = requestAnimationFrame(() => { const delay = performance.now() - currentTime; delays.push(delay); // 严格模式:限制数组大小 if (delays.length > 100) { delays.shift(); } // 计算统计 const average = delays.reduce((a, b) => a + b, 0) / delays.length; const max = Math.max(...delays); metrics.value = { scrollCount: metrics.value.scrollCount + 1, averageDelay: Math.round(average), maxDelay: Math.round(max), isJanky: average > 16.67 // 超过 60fps 阈值 }; if (average > 16.67) { console.warn(`[Scroll Jank] 滚动延迟过高: ${average.toFixed(2)}ms`); } }); }; onMounted(() => { const target = element || window; target.addEventListener('scroll', handleScroll, { passive: true }); }); onUnmounted(() => { const target = element || window; target.removeEventListener('scroll', handleScroll); if (rafId !== null) { cancelAnimationFrame(rafId); } }); return { metrics }; }场景四:复杂计算任务性能隔离
5.1 Web Worker 性能监控
// composables/useWorkerPerformance.ts import { ref, onUnmounted, type Ref } from 'vue'; export interface WorkerMetrics { taskCount: number; totalTime: number; averageTime: number; lastTaskDuration: number | null; } export interface WorkerTask<T = any> { id: string; payload: T; } export function useWorkerPerformance(workerPath: string) { const metrics: Ref<WorkerMetrics> = ref({ taskCount: 0, totalTime: 0, averageTime: 0, lastTaskDuration: null }); let worker: Worker | null = null; const isPerformanceSupported = () => typeof performance !== 'undefined' && typeof performance.now === 'function'; const executeTask = <T, R>(task: WorkerTask<T>): Promise<R> => { return new Promise((resolve, reject) => { if (!worker) { reject(new Error('Worker not initialized')); return; } const startTime = isPerformanceSupported() ? performance.now() : Date.now(); const handleMessage = (event: MessageEvent) => { const duration = isPerformanceSupported() ? performance.now() - startTime : Date.now() - startTime; // 更新指标 metrics.value.taskCount++; metrics.value.totalTime += duration; metrics.value.averageTime = metrics.value.totalTime / metrics.value.taskCount; metrics.value.lastTaskDuration = duration; // 性能警告 if (duration > 1000) { console.warn(`Worker 任务耗时过长: ${duration.toFixed(2)}ms`); } resolve(event.data); }; const handleError = (error: ErrorEvent) => { reject(new Error(`Worker error: ${error.message}`)); }; worker.addEventListener('message', handleMessage, { once: true }); worker.addEventListener('error', handleError, { once: true }); worker.postMessage(task); }); }; // 严格模式:初始化时检查环境 if (window.Worker && isPerformanceSupported()) { worker = new Worker(workerPath); } else { console.warn('Web Worker or Performance API not supported'); } onUnmounted(() => { if (worker) { worker.terminate(); } }); return { metrics: readonly(metrics), // 严格模式:防止外部修改 executeTask, isSupported: worker !== null }; } // Worker 脚本 (worker.ts) import type { WorkerTask } from '@/composables/useWorkerPerformance'; self.addEventListener('message', (event: MessageEvent<WorkerTask>) => { const { id, payload } = event.data; // 模拟复杂计算 const result = heavyComputation(payload); self.postMessage({ id, result }); }); function heavyComputation(data: any): any { // 实际计算逻辑 return data; }5.2 在 Vue 组件中使用
<template> <div> <canvas ref="canvas"></canvas> <div v-if="workerPerf.isSupported"> <p>任务数: {{ workerPerf.metrics.taskCount }}</p> <p>平均耗时: {{ workerPerf.metrics.averageTime.toFixed(2) }}ms</p> <p v-if="workerPerf.metrics.lastTaskDuration"> 上次任务: {{ workerPerf.metrics.lastTaskDuration.toFixed(2) }}ms </p> </div> </div> </template> <script setup lang="ts"> import { onMounted } from 'vue'; import { useWorkerPerformance } from '@/composables/useWorkerPerformance'; const workerPerf = useWorkerPerformance('/workers/image-processor.js'); onMounted(async () => { if (!workerPerf.isSupported) return; const result = await workerPerf.executeTask({ id: 'render-1', payload: { width: 1920, height: 1080 } }); console.log('Worker 任务完成:', result); }); </script>场景五:自定义指令性能监控
6.1 v-perf 指令
// directives/vPerf.ts import type { Directive } from 'vue'; interface PerfBinding { name: string; report?: boolean; threshold?: number; } export const vPerf: Directive<HTMLElement, PerfBinding> = { mounted(el, binding) { const { name, report = true, threshold = 100 } = binding.value; // 严格模式:检查性能 API if (typeof performance === 'undefined') return; const startTime = performance.now(); // 在下一个微任务中测量 Promise.resolve().then(() => { const duration = performance.now() - startTime; // 严格模式:阈值必须为数字 if (duration > (threshold as number)) { console.warn(`[v-perf] ${name} 渲染耗时: ${duration.toFixed(2)}ms`); if (report) { // 上报到监控系统 reportToAnalytics({ type: 'directive-perf', name, duration, timestamp: performance.now() }); } } }); } }; // 在 main.ts 中注册 app.directive('perf', vPerf); // 组件中使用 <template> <div v-perf="{ name: 'ComplexChart', threshold: 50 }"> <canvas ref="chartCanvas"></canvas> </div> </template>最佳实践总结
1.集中式 Performance API 检查
// utils/performance-guard.ts export function assertPerformanceAPI(): asserts performance is Performance { if (typeof performance === 'undefined') { throw new Error('Performance API is not supported in this environment'); } } // 在 Composable 中使用 export function usePreciseTimer() { try { assertPerformanceAPI(); // TypeScript 现在知道 performance 一定存在 const start = performance.now(); return { end: () => performance.now() - start }; } catch { // 降级方案 const start = Date.now(); return { end: () => Date.now() - start }; } }2.Vue 3 与 PerformanceObserver 集成
// composables/useWebVitals.ts import { onMounted, onUnmounted } from 'vue'; export function useWebVitals() { onMounted(() => { if ('PerformanceObserver' in window) { // LCP const lcpObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); const lastEntry = entries[entries.length - 1]; if (lastEntry) { console.log('LCP:', lastEntry.startTime); // 上报数据... } }); lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true }); onUnmounted(() => { lcpObserver.disconnect(); }); } }); }3.性能数据持久化与可视化
// stores/performanceDashboard.ts export const usePerformanceDashboard = defineStore('performance-dashboard', () => { const realtimeMetrics = ref<PerformanceMetrics[]>([]); // 使用 BroadcastChannel 跨标签页同步 const bc = new BroadcastChannel('perf-channel'); bc.onmessage = (event) => { const metric = event.data as PerformanceMetrics; realtimeMetrics.value.push(metric); // 严格模式:限制数据量 if (realtimeMetrics.value.length > 1000) { realtimeMetrics.value = realtimeMetrics.value.slice(-500); } }; return { realtimeMetrics }; });性能与精度考量
采样率控制:生产环境建议 10% 采样,避免影响用户体验
非阻塞上报:使用
requestIdleCallback延迟上报内存管理:及时清理 performance entry,避免内存泄漏
// 严格模式:确保清理函数存在 if (performance.clearResourceTimings) { performance.clearResourceTimings(); }通过 Vue 3 的响应式系统与 TypeScript 严格模式的结合,我们不仅获得了精确的性能测量能力,还确保了代码的健壮性和可维护性。这种"监控即代码"的实践,让性能优化成为开发流程的自然组成部分。