在 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 类型增强
TypeScript
// 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
TypeScript
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 的全局请求性能监控
TypeScript
// 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 请求拦截器集成
TypeScript
// 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 监控
TypeScript
// 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 滚动性能监控
TypeScript
// 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 性能监控
TypeScript
// 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 组件中使用
TypeScript
<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 指令
TypeScript
// 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 检查
TypeScript
// 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 集成
TypeScript
// 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. 性能数据持久化与可视化
TypeScript
// 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,避免内存泄漏
TypeScript
// 严格模式:确保清理函数存在
if (performance.clearResourceTimings) {
performance.clearResourceTimings();
}
通过 Vue 3 的响应式系统与 TypeScript 严格模式的结合,我们不仅获得了精确的性能测量能力,还确保了代码的健壮性和可维护性。这种"监控即代码"的实践,让性能优化成为开发流程的自然组成部分。