性能优化基础

性能优化是前端开发中至关重要的一环。良好的性能不仅能提升用户体验,还能影响搜索引擎排名和转化率。本章将介绍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('需要优化');
    }
}

性能指标体系

核心Web指标(Core Web Vitals)

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
    }
};

Performance API

performance对象

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';
            }
        });
    });
}

使用CSS硬件加速

.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();
});

东巴文小贴士

🔍 性能优化三原则

  1. 测量先行:不要凭直觉优化,用数据说话
  2. 关注用户:优化用户感知到的性能,而非单纯的指标
  3. 持续监控:性能不是一次性的工作,需要持续关注

性能预算

设定性能预算可以帮助团队在开发过程中保持性能意识:

  • JavaScript: < 200KB (压缩后)
  • CSS: < 100KB
  • 图片: < 500KB (首屏)
  • LCP: < 2.5s
  • FID: < 100ms

下一步

下一章将深入探讨 JavaScript优化,学习如何优化JavaScript代码的执行效率,包括算法优化、内存管理和运行时性能调优。