性能优化是前端开发中至关重要的一环。良好的性能不仅能提升用户体验,还能影响搜索引擎排名和转化率。本章将介绍Web性能优化的核心概念和基础方法。
| 指标 | 性能影响 | 业务结果 |
|---|---|---|
| 页面加载时间 | 每增加1秒 | 转化率下降7% |
| 首次内容渲染 | 超过3秒 | 53%用户放弃访问 |
| 交互延迟 | 超过100ms | 用户感知到卡顿 |
| 动画帧率 | 低于60fps | 动画不流畅 |
const perceptionThresholds = {
instant: 100, // 100ms内:即时响应
smooth: 300, // 300ms内:流畅过渡
acceptable: 1000, // 1秒内:可接受等待
frustrating: 3000 // 3秒以上:用户开始焦虑
};
function measureResponseTime(callback) {
const start = performance.now();
callback();
const duration = performance.now() - start;
if (duration < perceptionThresholds.instant) {
console.log('即时响应');
} else if (duration < perceptionThresholds.smooth) {
console.log('流畅响应');
} else if (duration < perceptionThresholds.acceptable) {
console.log('可接受');
} else {
console.log('需要优化');
}
}
Google定义的三个核心指标:
const coreWebVitals = {
LCP: {
name: 'Largest Contentful Paint',
description: '最大内容渲染时间',
good: 2500, // 2.5秒内
needsImprovement: 4000, // 4秒内
measure: () => {
return new Promise((resolve) => {
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
resolve(lastEntry.startTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });
});
}
},
FID: {
name: 'First Input Delay',
description: '首次输入延迟',
good: 100, // 100ms内
needsImprovement: 300, // 300ms内
measure: () => {
return new Promise((resolve) => {
new PerformanceObserver((list) => {
const entries = list.getEntries();
resolve(entries[0].processingStart - entries[0].startTime);
}).observe({ type: 'first-input', buffered: true });
});
}
},
CLS: {
name: 'Cumulative Layout Shift',
description: '累积布局偏移',
good: 0.1, // 0.1内
needsImprovement: 0.25, // 0.25内
measure: () => {
let clsScore = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsScore += entry.value;
}
}
}).observe({ type: 'layout-shift', buffered: true });
return clsScore;
}
}
};
const otherMetrics = {
FCP: {
name: 'First Contentful Paint',
description: '首次内容渲染',
good: 1800
},
TTFB: {
name: 'Time to First Byte',
description: '首字节时间',
good: 800
},
TTI: {
name: 'Time to Interactive',
description: '可交互时间',
good: 3800
},
TBT: {
name: 'Total Blocking Time',
description: '总阻塞时间',
good: 200
}
};
console.log(performance);
performance.getEntriesByType('navigation')[0];
performance.getEntriesByType('resource');
performance.getEntriesByType('paint');
performance.getEntriesByType('measure');
performance.mark('start');
for (let i = 0; i < 1000000; i++) {
Math.sqrt(i);
}
performance.mark('end');
performance.measure('计算耗时', 'start', 'end');
const measures = performance.getEntriesByName('计算耗时');
console.log(`执行时间: ${measures[0].duration}ms`);
performance.clearMarks();
performance.clearMeasures();
class PerformanceTracker {
constructor() {
this.marks = new Map();
this.measures = [];
}
start(name) {
this.marks.set(name, performance.now());
performance.mark(`${name}-start`);
}
end(name) {
const startTime = this.marks.get(name);
if (!startTime) {
console.warn(`未找到标记: ${name}`);
return null;
}
performance.mark(`${name}-end`);
performance.measure(name, `${name}-start`, `${name}-end`);
const entries = performance.getEntriesByName(name);
const duration = entries[entries.length - 1].duration;
this.measures.push({
name,
duration,
timestamp: Date.now()
});
this.marks.delete(name);
return duration;
}
getMeasures(name) {
if (name) {
return this.measures.filter(m => m.name === name);
}
return this.measures;
}
getAverageTime(name) {
const measures = this.getMeasures(name);
if (measures.length === 0) return 0;
const total = measures.reduce((sum, m) => sum + m.duration, 0);
return total / measures.length;
}
report() {
console.table(this.measures.map(m => ({
操作: m.name,
耗时: `${m.duration.toFixed(2)}ms`,
时间: new Date(m.timestamp).toLocaleTimeString()
})));
}
}
const tracker = new PerformanceTracker();
tracker.start('数据处理');
processLargeData();
tracker.end('数据处理');
tracker.report();
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="hero-image.jpg" as="image">
<link rel="preload" href="main.js" as="script">
<link rel="prefetch" href="next-page.html">
<link rel="prefetch" href="next-page-data.json">
<link rel="preconnect" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
<script src="non-critical.js" defer></script>
<script src="analytics.js" async></script>
const resourcePriorities = {
critical: {
resources: ['HTML', '关键CSS', '首屏字体'],
strategy: '立即加载,阻塞渲染',
techniques: ['内联', 'preload', '服务端渲染']
},
high: {
resources: ['首屏图片', '主要JS'],
strategy: '优先加载,不阻塞',
techniques: ['preload', 'fetchPriority="high"']
},
low: {
resources: ['非首屏图片', '分析脚本'],
strategy: '延迟加载',
techniques: ['lazy', 'defer', 'requestIdleCallback']
}
};
const imageOptimization = {
format: {
avif: {
support: 'Chrome 85+, Firefox 93+',
compression: '比WebP小20%',
fallback: 'WebP → JPEG'
},
webp: {
support: 'Chrome 23+, Firefox 65+',
compression: '比JPEG小25-35%',
fallback: 'JPEG/PNG'
}
},
responsive: `
<picture>
<source
type="image/avif"
srcset="image-400.avif 400w,
image-800.avif 800w,
image-1200.avif 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1000px) 800px,
1200px">
<source
type="image/webp"
srcset="image-400.webp 400w,
image-800.webp 800w,
image-1200.webp 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1000px) 800px,
1200px">
<img
src="image-800.jpg"
srcset="image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1000px) 800px,
1200px"
alt="响应式图片"
loading="lazy"
decoding="async">
</picture>
`,
lazyLoading: `
<img loading="lazy" src="image.jpg" alt="懒加载图片">
<iframe loading="lazy" src="video.html"></iframe>
`
};
const criticalRenderingPath = {
steps: [
'DOM构建: HTML → DOM树',
'CSSOM构建: CSS → CSSOM树',
'渲染树: DOM + CSSOM → 渲染树',
'布局: 计算元素位置和大小',
'绘制: 像素绘制到屏幕',
'合成: 图层合成'
],
optimization: {
dom: '减少DOM节点数量',
css: '精简CSS,避免@import',
renderTree: '隐藏元素不进入渲染树',
layout: '避免强制同步布局',
paint: '减少绘制区域',
composite: '使用transform和opacity'
}
};
function badExample() {
const elements = document.querySelectorAll('.item');
for (let i = 0; i < elements.length; i++) {
const height = elements[i].offsetHeight;
elements[i].style.height = height + 10 + 'px';
}
}
function goodExample() {
const elements = document.querySelectorAll('.item');
const heights = [];
for (let i = 0; i < elements.length; i++) {
heights.push(elements[i].offsetHeight);
}
for (let i = 0; i < elements.length; i++) {
elements[i].style.height = heights[i] + 10 + 'px';
}
}
function betterExample() {
requestAnimationFrame(() => {
const elements = document.querySelectorAll('.item');
const heights = [];
for (let i = 0; i < elements.length; i++) {
heights.push(elements[i].offsetHeight);
}
requestAnimationFrame(() => {
for (let i = 0; i < elements.length; i++) {
elements[i].style.height = heights[i] + 10 + 'px';
}
});
});
}
.accelerated {
transform: translateZ(0);
will-change: transform;
}
.animated-element {
transition: transform 0.3s ease;
}
.animated-element:hover {
transform: translateX(100px) scale(1.1);
}
@keyframes slide {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100px);
opacity: 0.8;
}
}
const memoryLeaks = {
commonCauses: [
'未清理的定时器',
'未移除的事件监听器',
'闭包引用',
'DOM引用',
'控制台日志'
],
detection: `
// Chrome DevTools
// 1. 打开 Memory 面板
// 2. 选择 "Heap snapshot"
// 3. 执行操作前后对比
// 使用 performance.memory (仅Chrome)
function checkMemory() {
if (performance.memory) {
console.log({
usedJSHeapSize: (performance.memory.usedJSHeapSize / 1048576).toFixed(2) + ' MB',
totalJSHeapSize: (performance.memory.totalJSHeapSize / 1048576).toFixed(2) + ' MB',
limit: (performance.memory.jsHeapSizeLimit / 1048576).toFixed(2) + ' MB'
});
}
}
`,
prevention: `
// 清理定时器
const timer = setInterval(callback, 1000);
clearInterval(timer);
// 移除事件监听器
const handler = () => {};
element.addEventListener('click', handler);
element.removeEventListener('click', handler);
// 使用WeakMap避免强引用
const cache = new WeakMap();
cache.set(element, data);
`
};
class ObjectPool {
constructor(factory, initialSize = 10) {
this.factory = factory;
this.pool = [];
this.active = new Set();
for (let i = 0; i < initialSize; i++) {
this.pool.push(factory());
}
}
acquire() {
let obj;
if (this.pool.length > 0) {
obj = this.pool.pop();
} else {
obj = this.factory();
}
this.active.add(obj);
return obj;
}
release(obj) {
if (this.active.has(obj)) {
this.active.delete(obj);
if (typeof obj.reset === 'function') {
obj.reset();
}
this.pool.push(obj);
}
}
get stats() {
return {
pooled: this.pool.length,
active: this.active.size,
total: this.pool.length + this.active.size
};
}
}
const bulletPool = new ObjectPool(() => ({
x: 0,
y: 0,
velocity: { x: 0, y: 0 },
active: false,
reset() {
this.x = 0;
this.y = 0;
this.velocity = { x: 0, y: 0 };
this.active = false;
}
}), 50);
const bullet = bulletPool.acquire();
bullet.active = true;
bullet.x = player.x;
bullet.y = player.y;
bulletPool.release(bullet);
class PerformanceMonitor {
constructor() {
this.metrics = {};
this.observers = [];
}
observe(entryTypes) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.record(entry);
}
});
observer.observe({ entryTypes });
this.observers.push(observer);
}
record(entry) {
const type = entry.entryType;
if (!this.metrics[type]) {
this.metrics[type] = [];
}
this.metrics[type].push({
name: entry.name,
duration: entry.duration,
startTime: entry.startTime,
timestamp: Date.now()
});
}
getMetric(type, name) {
const entries = this.metrics[type] || [];
return entries.filter(e => e.name === name);
}
getSummary(type) {
const entries = this.metrics[type] || [];
if (entries.length === 0) return null;
const durations = entries.map(e => e.duration);
return {
count: entries.length,
avg: durations.reduce((a, b) => a + b, 0) / durations.length,
min: Math.min(...durations),
max: Math.max(...durations),
p50: this.percentile(durations, 50),
p95: this.percentile(durations, 95),
p99: this.percentile(durations, 99)
};
}
percentile(arr, p) {
const sorted = [...arr].sort((a, b) => a - b);
const index = Math.ceil(sorted.length * p / 100) - 1;
return sorted[index];
}
report() {
console.group('性能报告');
for (const [type, entries] of Object.entries(this.metrics)) {
console.log(`${type}: ${entries.length} 条记录`);
console.log(this.getSummary(type));
}
console.groupEnd();
}
disconnect() {
this.observers.forEach(o => o.disconnect());
this.observers = [];
}
}
const monitor = new PerformanceMonitor();
monitor.observe(['navigation', 'resource', 'paint', 'measure']);
class PerformanceReporter {
constructor(endpoint) {
this.endpoint = endpoint;
this.queue = [];
this.timer = null;
}
add(data) {
this.queue.push({
...data,
url: location.href,
userAgent: navigator.userAgent,
timestamp: Date.now()
});
this.scheduleSend();
}
scheduleSend() {
if (this.timer) return;
this.timer = setTimeout(() => {
this.send();
this.timer = null;
}, 5000);
}
async send() {
if (this.queue.length === 0) return;
const data = this.queue.splice(0);
if (navigator.sendBeacon) {
navigator.sendBeacon(this.endpoint, JSON.stringify(data));
} else {
fetch(this.endpoint, {
method: 'POST',
body: JSON.stringify(data),
keepalive: true
}).catch(() => {
this.queue.unshift(...data);
});
}
}
collectAndSend() {
const paintEntries = performance.getEntriesByType('paint');
const navigationEntry = performance.getEntriesByType('navigation')[0];
const metrics = {
FP: paintEntries.find(e => e.name === 'first-paint')?.startTime,
FCP: paintEntries.find(e => e.name === 'first-contentful-paint')?.startTime,
DNS: navigationEntry?.domainLookupEnd - navigationEntry?.domainLookupStart,
TCP: navigationEntry?.connectEnd - navigationEntry?.connectStart,
TTFB: navigationEntry?.responseStart - navigationEntry?.requestStart,
DOMReady: navigationEntry?.domContentLoadedEventEnd,
Load: navigationEntry?.loadEventEnd
};
this.add({ type: 'navigation', metrics });
}
}
const reporter = new PerformanceReporter('/api/performance');
window.addEventListener('load', () => {
setTimeout(() => reporter.collectAndSend(), 0);
});
window.addEventListener('beforeunload', () => {
reporter.send();
});
🔍 性能优化三原则
- 测量先行:不要凭直觉优化,用数据说话
- 关注用户:优化用户感知到的性能,而非单纯的指标
- 持续监控:性能不是一次性的工作,需要持续关注
⚡ 性能预算
设定性能预算可以帮助团队在开发过程中保持性能意识:
- JavaScript: < 200KB (压缩后)
- CSS: < 100KB
- 图片: < 500KB (首屏)
- LCP: < 2.5s
- FID: < 100ms
下一章将深入探讨 JavaScript优化,学习如何优化JavaScript代码的执行效率,包括算法优化、内存管理和运行时性能调优。