Performance API 完全指南
概述
Performance API 是浏览器提供的一套强大接口,用于精确测量 Web 应用的各项性能指标。通过它,开发者可以获取页面加载时间、资源下载耗时、用户交互延迟等关键数据,为性能优化提供数据支撑。
Performance API 核心概念
API 架构总览
Performance API 体系结构
window.performance
├── timing (已废弃,使用 Navigation Timing Level 2)
├── navigation
├── memory (Chrome)
├── timeOrigin
└── 方法
├── now() - 高精度时间戳
├── mark() - 创建标记
├── measure() - 测量时间段
├── getEntries() - 获取所有条目
├── getEntriesByType() - 按类型获取
└── getEntriesByName() - 按名称获取
PerformanceEntry 类型
├── navigation - 页面导航信息
├── resource - 资源加载信息
├── paint - 绘制时间点
├── mark - 自定义标记
├── measure - 自定义测量
├── longtask - 长任务
├── largest-contentful-paint - LCP
├── first-input - FID
└── layout-shift - CLS
基础用法
// 获取高精度时间戳
const startTime = performance.now()
// 执行一些操作
doSomething()
const endTime = performance.now()
console.log(`操作耗时: ${endTime - startTime}ms`)
// 获取页面加载性能数据
const navEntry = performance.getEntriesByType('navigation')[0]
console.log('DOM 加载完成:', navEntry.domContentLoadedEventEnd)
console.log('页面完全加载:', navEntry.loadEventEnd)
// 获取所有资源加载数据
const resources = performance.getEntriesByType('resource')
resources.forEach(resource => {
console.log(`${resource.name}: ${resource.duration}ms`)
})
Navigation Timing API
导航时间线详解
// Navigation Timing Level 2 (推荐使用)
const [navEntry] = performance.getEntriesByType('navigation')
// 完整时间线
const navigationTimeline = {
// DNS 查询
dns: {
start: navEntry.domainLookupStart,
end: navEntry.domainLookupEnd,
duration: navEntry.domainLookupEnd - navEntry.domainLookupStart
},
// TCP 连接
tcp: {
start: navEntry.connectStart,
end: navEntry.connectEnd,
duration: navEntry.connectEnd - navEntry.connectStart
},
// SSL/TLS 握手
ssl: {
start: navEntry.secureConnectionStart,
end: navEntry.connectEnd,
duration: navEntry.secureConnectionStart > 0
? navEntry.connectEnd - navEntry.secureConnectionStart
: 0
},
// 请求/响应
request: {
start: navEntry.requestStart,
end: navEntry.responseStart,
duration: navEntry.responseStart - navEntry.requestStart
},
response: {
start: navEntry.responseStart,
end: navEntry.responseEnd,
duration: navEntry.responseEnd - navEntry.responseStart
},
// DOM 处理
domProcessing: {
start: navEntry.responseEnd,
end: navEntry.domContentLoadedEventEnd,
duration: navEntry.domContentLoadedEventEnd - navEntry.responseEnd
},
// 页面完全加载
pageLoad: {
start: navEntry.startTime,
end: navEntry.loadEventEnd,
duration: navEntry.loadEventEnd - navEntry.startTime
}
}
关键性能指标提取
// 提取关键性能指标
function getNavigationMetrics() {
const [nav] = performance.getEntriesByType('navigation')
if (!nav) return null
return {
// 重定向时间
redirect: nav.redirectEnd - nav.redirectStart,
// DNS 查询时间
dns: nav.domainLookupEnd - nav.domainLookupStart,
// TCP 连接时间
tcp: nav.connectEnd - nav.connectStart,
// 首字节时间 (TTFB)
ttfb: nav.responseStart - nav.requestStart,
// 内容下载时间
download: nav.responseEnd - nav.responseStart,
// DOM 解析时间
domParse: nav.domInteractive - nav.responseEnd,
// DOM 完成时间
domComplete: nav.domComplete - nav.domInteractive,
// DOMContentLoaded 时间
domContentLoaded: nav.domContentLoadedEventEnd - nav.startTime,
// 页面完全加载时间
load: nav.loadEventEnd - nav.startTime,
// 传输类型
transferSize: nav.transferSize,
encodedBodySize: nav.encodedBodySize,
decodedBodySize: nav.decodedBodySize
}
}
// 使用示例
window.addEventListener('load', () => {
// 确保所有数据都已记录
setTimeout(() => {
const metrics = getNavigationMetrics()
console.table(metrics)
}, 0)
})
Resource Timing API
资源加载分析
// 获取所有资源加载信息
function analyzeResources() {
const resources = performance.getEntriesByType('resource')
// 按类型分组
const byType = resources.reduce((acc, r) => {
const type = r.initiatorType
if (!acc[type]) {
acc[type] = { count: 0, totalTime: 0, totalSize: 0, resources: [] }
}
acc[type].count++
acc[type].totalTime += r.duration
acc[type].totalSize += r.transferSize || 0
acc[type].resources.push({
name: r.name,
duration: r.duration,
size: r.transferSize
})
return acc
}, {})
return byType
}
// 找出加载最慢的资源
function getSlowestResources(limit = 10) {
const resources = performance.getEntriesByType('resource')
return resources
.sort((a, b) => b.duration - a.duration)
.slice(0, limit)
.map(r => ({
name: r.name.split('/').pop(),
fullUrl: r.name,
duration: Math.round(r.duration),
size: r.transferSize,
type: r.initiatorType
}))
}
// 资源加载时间线可视化数据
function getResourceTimeline() {
const resources = performance.getEntriesByType('resource')
return resources.map(r => ({
name: r.name.split('/').pop(),
startTime: r.startTime,
duration: r.duration,
endTime: r.startTime + r.duration,
phases: {
redirect: r.redirectEnd - r.redirectStart,
dns: r.domainLookupEnd - r.domainLookupStart,
tcp: r.connectEnd - r.connectStart,
request: r.responseStart - r.requestStart,
response: r.responseEnd - r.responseStart
}
}))
}
资源加载问题诊断
// 诊断资源加载问题
function diagnoseResourceIssues() {
const resources = performance.getEntriesByType('resource')
const issues = []
resources.forEach(r => {
// 问题 1: 慢 DNS 查询
const dnsTime = r.domainLookupEnd - r.domainLookupStart
if (dnsTime > 100) {
issues.push({
type: 'slow-dns',
resource: r.name,
time: dnsTime,
suggestion: '考虑使用 DNS 预解析: <link rel="dns-prefetch">'
})
}
// 问题 2: 慢 TCP 连接
const tcpTime = r.connectEnd - r.connectStart
if (tcpTime > 100) {
issues.push({
type: 'slow-tcp',
resource: r.name,
time: tcpTime,
suggestion: '考虑使用 preconnect: <link rel="preconnect">'
})
}
// 问题 3: 慢 TTFB
const ttfb = r.responseStart - r.requestStart
if (ttfb > 200) {
issues.push({
type: 'slow-ttfb',
resource: r.name,
time: ttfb,
suggestion: '检查服务器响应时间,考虑使用 CDN'
})
}
// 问题 4: 大文件
if (r.transferSize > 500000) { // 500KB
issues.push({
type: 'large-file',
resource: r.name,
size: r.transferSize,
suggestion: '考虑压缩或分割大文件'
})
}
// 问题 5: 未压缩
if (r.encodedBodySize && r.decodedBodySize) {
const compressionRatio = r.encodedBodySize / r.decodedBodySize
if (compressionRatio > 0.9 && r.decodedBodySize > 10000) {
issues.push({
type: 'not-compressed',
resource: r.name,
suggestion: '启用 Gzip/Brotli 压缩'
})
}
}
})
return issues
}
Paint Timing API
获取绘制时间点
// 获取首次绘制和首次内容绘制时间
function getPaintMetrics() {
const paintEntries = performance.getEntriesByType('paint')
const metrics = {}
paintEntries.forEach(entry => {
if (entry.name === 'first-paint') {
metrics.firstPaint = entry.startTime
}
if (entry.name === 'first-contentful-paint') {
metrics.firstContentfulPaint = entry.startTime
}
})
return metrics
}
// 使用 PerformanceObserver 实时监听
const paintObserver = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log(`${entry.name}: ${entry.startTime}ms`)
// 发送到分析服务
sendToAnalytics({
metric: entry.name,
value: entry.startTime
})
})
})
paintObserver.observe({ entryTypes: ['paint'] })
Core Web Vitals 测量
LCP (Largest Contentful Paint)
// 监测 LCP
function observeLCP() {
let lcpValue = 0
const lcpObserver = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries()
// 取最后一个 LCP 条目(最终值)
const lastEntry = entries[entries.length - 1]
lcpValue = lastEntry.startTime
console.log('LCP:', lcpValue, 'ms')
console.log('LCP 元素:', lastEntry.element)
})
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] })
// 页面隐藏时停止观察并上报
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
lcpObserver.disconnect()
reportMetric('LCP', lcpValue)
}
})
}
// LCP 评级
function rateLCP(value) {
if (value <= 2500) return 'good'
if (value <= 4000) return 'needs-improvement'
return 'poor'
}
FID (First Input Delay)
// 监测 FID
function observeFID() {
const fidObserver = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries()
entries.forEach(entry => {
const fidValue = entry.processingStart - entry.startTime
console.log('FID:', fidValue, 'ms')
console.log('输入类型:', entry.name)
reportMetric('FID', fidValue)
})
})
fidObserver.observe({ entryTypes: ['first-input'] })
}
// FID 评级
function rateFID(value) {
if (value <= 100) return 'good'
if (value <= 300) return 'needs-improvement'
return 'poor'
}
CLS (Cumulative Layout Shift)
// 监测 CLS
function observeCLS() {
let clsValue = 0
let sessionValue = 0
let sessionEntries = []
const clsObserver = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// 只计算没有用户输入的布局偏移
if (!entry.hadRecentInput) {
const firstSessionEntry = sessionEntries[0]
const lastSessionEntry = sessionEntries[sessionEntries.length - 1]
// 如果与上一次偏移间隔不超过 1 秒,且与第一次偏移间隔不超过 5 秒
// 则归入同一会话
if (
sessionValue &&
entry.startTime - lastSessionEntry.startTime < 1000 &&
entry.startTime - firstSessionEntry.startTime < 5000
) {
sessionValue += entry.value
sessionEntries.push(entry)
} else {
sessionValue = entry.value
sessionEntries = [entry]
}
// 更新最大会话值
if (sessionValue > clsValue) {
clsValue = sessionValue
}
}
}
console.log('当前 CLS:', clsValue)
})
clsObserver.observe({ entryTypes: ['layout-shift'] })
// 页面隐藏时上报
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
clsObserver.disconnect()
reportMetric('CLS', clsValue)
}
})
}
// CLS 评级
function rateCLS(value) {
if (value <= 0.1) return 'good'
if (value <= 0.25) return 'needs-improvement'
return 'poor'
}
User Timing API
自定义性能标记
// 创建性能标记
performance.mark('app-init-start')
// 初始化应用
initializeApp()
performance.mark('app-init-end')
// 测量两个标记之间的时间
performance.measure('app-init-duration', 'app-init-start', 'app-init-end')
// 获取测量结果
const measures = performance.getEntriesByName('app-init-duration')
console.log('应用初始化耗时:', measures[0].duration, 'ms')
组件渲染性能追踪
// Vue/React 组件性能追踪示例
class PerformanceTracker {
constructor() {
this.marks = new Map()
}
// 开始追踪
startTrack(name) {
const markName = `${name}-start`
performance.mark(markName)
this.marks.set(name, markName)
}
// 结束追踪
endTrack(name) {
const startMark = this.marks.get(name)
if (!startMark) return null
const endMark = `${name}-end`
performance.mark(endMark)
const measureName = `${name}-duration`
performance.measure(measureName, startMark, endMark)
const [measure] = performance.getEntriesByName(measureName)
// 清理
performance.clearMarks(startMark)
performance.clearMarks(endMark)
performance.clearMeasures(measureName)
this.marks.delete(name)
return measure?.duration
}
// 追踪异步操作
async trackAsync(name, asyncFn) {
this.startTrack(name)
try {
const result = await asyncFn()
const duration = this.endTrack(name)
console.log(`${name}: ${duration}ms`)
return result
} catch (error) {
this.endTrack(name)
throw error
}
}
}
// 使用示例
const tracker = new PerformanceTracker()
// 追踪组件渲染
tracker.startTrack('UserList-render')
renderUserList()
const renderTime = tracker.endTrack('UserList-render')
console.log('UserList 渲染耗时:', renderTime)
// 追踪 API 调用
const users = await tracker.trackAsync('fetch-users', () =>
fetch('/api/users').then(r => r.json())
)
Long Tasks API
监测长任务
// 监测长任务(超过 50ms 的任务)
const longTaskObserver = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log('检测到长任务:', {
duration: entry.duration,
startTime: entry.startTime,
name: entry.name
})
// 如果任务超过 100ms,记录警告
if (entry.duration > 100) {
console.warn('严重长任务:', entry.duration, 'ms')
// 上报到分析服务
reportMetric('long-task', {
duration: entry.duration,
url: window.location.href
})
}
})
})
longTaskObserver.observe({ entryTypes: ['longtask'] })
长任务分析与优化
// 长任务分析工具
class LongTaskAnalyzer {
constructor() {
this.tasks = []
this.observer = null
}
start() {
this.observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
this.tasks.push({
duration: entry.duration,
startTime: entry.startTime,
timestamp: Date.now()
})
})
})
this.observer.observe({ entryTypes: ['longtask'] })
}
stop() {
if (this.observer) {
this.observer.disconnect()
}
}
getReport() {
if (this.tasks.length === 0) {
return { message: '没有检测到长任务' }
}
const totalDuration = this.tasks.reduce((sum, t) => sum + t.duration, 0)
const avgDuration = totalDuration / this.tasks.length
const maxDuration = Math.max(...this.tasks.map(t => t.duration))
return {
count: this.tasks.length,
totalDuration: Math.round(totalDuration),
avgDuration: Math.round(avgDuration),
maxDuration: Math.round(maxDuration),
tasks: this.tasks,
recommendation: this.getRecommendation(avgDuration)
}
}
getRecommendation(avgDuration) {
if (avgDuration > 100) {
return '存在严重的主线程阻塞,建议:1) 代码分割 2) Web Worker 3) 优化关键路径'
}
if (avgDuration > 50) {
return '有一些长任务,建议检查关键操作的执行时间'
}
return '长任务情况正常'
}
}
完整性能监控方案
集成监控类
// 完整的性能监控类
class PerformanceMonitor {
constructor(options = {}) {
this.options = {
reportUrl: options.reportUrl || '/api/performance',
sampleRate: options.sampleRate || 1, // 采样率
enableLongTask: options.enableLongTask ?? true,
enableResourceTiming: options.enableResourceTiming ?? true,
...options
}
this.metrics = {}
this.observers = []
}
init() {
// 采样控制
if (Math.random() > this.options.sampleRate) {
return
}
this.observeNavigation()
this.observePaint()
this.observeWebVitals()
if (this.options.enableLongTask) {
this.observeLongTasks()
}
// 页面卸载时上报
window.addEventListener('beforeunload', () => {
this.report()
})
// 页面隐藏时上报(移动端更可靠)
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
this.report()
}
})
}
observeNavigation() {
window.addEventListener('load', () => {
setTimeout(() => {
const [nav] = performance.getEntriesByType('navigation')
if (nav) {
this.metrics.navigation = {
dns: nav.domainLookupEnd - nav.domainLookupStart,
tcp: nav.connectEnd - nav.connectStart,
ttfb: nav.responseStart - nav.requestStart,
download: nav.responseEnd - nav.responseStart,
domReady: nav.domContentLoadedEventEnd - nav.startTime,
load: nav.loadEventEnd - nav.startTime
}
}
}, 0)
})
}
observePaint() {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
this.metrics[entry.name] = entry.startTime
})
})
observer.observe({ entryTypes: ['paint'] })
this.observers.push(observer)
}
observeWebVitals() {
// LCP
this.observeLCP()
// FID
this.observeFID()
// CLS
this.observeCLS()
}
observeLCP() {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries()
const lastEntry = entries[entries.length - 1]
this.metrics.lcp = lastEntry.startTime
})
observer.observe({ entryTypes: ['largest-contentful-paint'] })
this.observers.push(observer)
}
observeFID() {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries()
if (entries.length > 0) {
const entry = entries[0]
this.metrics.fid = entry.processingStart - entry.startTime
}
})
observer.observe({ entryTypes: ['first-input'] })
this.observers.push(observer)
}
observeCLS() {
let clsValue = 0
let sessionValue = 0
let sessionEntries = []
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
const firstSessionEntry = sessionEntries[0]
const lastSessionEntry = sessionEntries[sessionEntries.length - 1]
if (
sessionValue &&
entry.startTime - lastSessionEntry?.startTime < 1000 &&
entry.startTime - firstSessionEntry?.startTime < 5000
) {
sessionValue += entry.value
sessionEntries.push(entry)
} else {
sessionValue = entry.value
sessionEntries = [entry]
}
if (sessionValue > clsValue) {
clsValue = sessionValue
}
}
}
this.metrics.cls = clsValue
})
observer.observe({ entryTypes: ['layout-shift'] })
this.observers.push(observer)
}
observeLongTasks() {
const observer = new PerformanceObserver((list) => {
const tasks = list.getEntries()
if (!this.metrics.longTasks) {
this.metrics.longTasks = []
}
tasks.forEach(task => {
this.metrics.longTasks.push({
duration: task.duration,
startTime: task.startTime
})
})
})
observer.observe({ entryTypes: ['longtask'] })
this.observers.push(observer)
}
getMetrics() {
return {
...this.metrics,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: Date.now()
}
}
report() {
const metrics = this.getMetrics()
// 使用 sendBeacon 确保数据发送
if (navigator.sendBeacon) {
navigator.sendBeacon(
this.options.reportUrl,
JSON.stringify(metrics)
)
} else {
// 降级方案
fetch(this.options.reportUrl, {
method: 'POST',
body: JSON.stringify(metrics),
keepalive: true
}).catch(() => {})
}
}
destroy() {
this.observers.forEach(observer => observer.disconnect())
this.observers = []
}
}
// 使用示例
const monitor = new PerformanceMonitor({
reportUrl: '/api/performance',
sampleRate: 0.1 // 10% 采样
})
monitor.init()
最佳实践
性能监控注意事项
// 性能监控最佳实践
const bestPractices = {
// 1. 使用采样减少开销
sampling: {
description: '不需要收集 100% 用户的数据',
recommendation: '生产环境使用 1-10% 采样率',
code: 'if (Math.random() < 0.1) { monitor.init() }'
},
// 2. 使用 sendBeacon 上报
reporting: {
description: 'sendBeacon 不会阻塞页面卸载',
recommendation: '始终优先使用 sendBeacon',
fallback: 'fetch with keepalive: true'
},
// 3. 在 visibilitychange 时上报
timing: {
description: 'beforeunload 在移动端不可靠',
recommendation: '同时监听 visibilitychange',
code: 'document.addEventListener("visibilitychange", ...)'
},
// 4. 延迟初始化
initialization: {
description: '避免影响页面初始加载',
recommendation: '在 load 事件后或 requestIdleCallback 中初始化',
code: 'requestIdleCallback(() => monitor.init())'
},
// 5. 数据去重
deduplication: {
description: '同一页面可能多次上报',
recommendation: '在服务端进行去重处理'
}
}
常见问题解答
Q: Performance API 在哪些浏览器中可用? A: 主要 API 在现代浏览器中广泛支持。Navigation Timing Level 2、Resource Timing、User Timing 支持 IE10+。Web Vitals 相关 API 主要支持 Chromium 浏览器。
Q: performance.now() 和 Date.now() 有什么区别? A: performance.now() 返回高精度时间戳(微秒级),从页面导航开始计时,不受系统时间调整影响。Date.now() 返回毫秒级时间戳,会受系统时间变化影响。
Q: 性能数据采集会影响页面性能吗? A: Performance API 本身开销很小。但要注意:1) 不要频繁调用 getEntries();2) 使用 PerformanceObserver 代替轮询;3) 采样上报减少网络请求。
Q: 如何处理 Performance API 不支持的浏览器?
A: 使用特性检测:if ('performance' in window && 'getEntriesByType' in performance),不支持时降级或跳过监控。
总结
Performance API 是 Web 性能监控的核心工具。通过合理使用这些 API,可以:
- 精确测量页面加载性能 - Navigation Timing
- 分析资源加载瓶颈 - Resource Timing
- 监控用户体验指标 - Web Vitals APIs
- 追踪自定义性能点 - User Timing
- 发现主线程阻塞 - Long Tasks API
核心要点:
- 使用 PerformanceObserver 而非轮询
- 关注 Core Web Vitals (LCP, FID, CLS)
- 实现采样和 sendBeacon 上报
- 建立完整的性能监控体系


