前端页面加载时间优化的实战经验分享

Good“爱霖 前端 阅读 807
赞 91 收藏
二维码
手机扫码查看
反馈

性能监控的核心数据怎么拿?

前端性能优化绕不开页面加载时间统计,这个数据直接影响用户体验。我之前做个项目,老板天天问页面到底多快,用户等待时间长不长,后来直接上了Performance API来精确测量。

前端页面加载时间优化的实战经验分享

核心代码其实很简单,就是在window.onload事件里获取各种时间节点:

window.addEventListener('load', function() {
    const timing = performance.timing;
    
    // 各种时间计算
    const dnsTime = timing.domainLookupEnd - timing.domainLookupStart;
    const tcpTime = timing.connectEnd - timing.connectStart;
    const requestTime = timing.responseEnd - timing.requestStart;
    const domReadyTime = timing.domContentLoadedEventEnd - timing.navigationStart;
    const pageLoadTime = timing.loadEventEnd - timing.navigationStart;
    
    console.log({
        DNS查询时间: dnsTime,
        TCP连接时间: tcpTime,
        请求响应时间: requestTime,
        DOM准备时间: domReadyTime,
        页面完全加载: pageLoadTime
    });
});

实际项目里的完整实现

上面那个是基础版本,真正上线肯定不能这么粗糙。我在jztheme.com的项目里用的是更完善的封装:

class PageLoadTracker {
    constructor() {
        this.init();
    }
    
    init() {
        if (performance.timing) {
            window.addEventListener('load', () => {
                setTimeout(() => {
                    this.collectAndSend();
                }, 0); // 确保DOM完全渲染完成
            });
        }
    }
    
    collectAndSend() {
        const timing = performance.timing;
        
        const data = {
            navigationStart: timing.navigationStart,
            unloadEventStart: timing.unloadEventStart,
            unloadEventEnd: timing.unloadEventEnd,
            redirectStart: timing.redirectStart,
            redirectEnd: timing.redirectEnd,
            fetchStart: timing.fetchStart,
            domainLookupStart: timing.domainLookupStart,
            domainLookupEnd: timing.domainLookupEnd,
            connectStart: timing.connectStart,
            connectEnd: timing.connectEnd,
            secureConnectionStart: timing.secureConnectionStart,
            requestStart: timing.requestStart,
            responseStart: timing.responseStart,
            responseEnd: timing.responseEnd,
            domLoading: timing.domLoading,
            domInteractive: timing.domInteractive,
            domContentLoadedEventStart: timing.domContentLoadedEventStart,
            domContentLoadedEventEnd: timing.domContentLoadedEventEnd,
            domComplete: timing.domComplete,
            loadEventStart: timing.loadEventStart,
            loadEventEnd: timing.loadEventEnd
        };
        
        // 计算关键时间点
        const calculated = {
            DNS查询: data.domainLookupEnd - data.domainLookupStart,
            TCP连接: data.connectEnd - data.connectStart,
            SSL握手: data.secureConnectionStart ? data.connectEnd - data.secureConnectionStart : 0,
            首字节时间: data.responseStart - data.requestStart,
            请求响应: data.responseEnd - data.requestStart,
            DOM解析: data.domComplete - data.domInteractive,
            DOM准备: data.domContentLoadedEventEnd - data.navigationStart,
            页面加载: data.loadEventEnd - data.navigationStart,
            总耗时: data.loadEventEnd - data.navigationStart
        };
        
        // 发送到监控服务
        this.sendToMonitor(calculated);
    }
    
    sendToMonitor(data) {
        // 实际项目中替换为真实的上报接口
        fetch('https://jztheme.com/api/performance', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                url: window.location.href,
                timestamp: Date.now(),
                ...data
            })
        }).catch(err => {
            console.error('性能数据上报失败:', err);
        });
    }
}

// 初始化
new PageLoadTracker();

这里注意我踩过好几次坑

第一,Timing API有个很大的限制:跨域资源的时间信息会被截断。如果请求不是同源的,responseStart、responseEnd这些字段都会变成0。这个问题我折腾了半天才发现。

第二,Timing API在某些老浏览器里支持不好,所以得加兼容判断:

if (performance.timing && performance.getEntriesByType) {
    // 现代浏览器用 Navigation Timing Level 2
    const navigation = performance.getEntriesByType('navigation')[0];
    if (navigation) {
        console.log(navigation);
    }
} else if (performance.timing) {
    // 老版本用 Timing API
    console.log(performance.timing);
}

Navigation Timing Level 2确实更简洁,返回的是一个对象而不是一堆时间戳,但兼容性没Timing API好。建议两个都兼容一下。

更精确的资源加载监控

除了页面加载时间,资源加载情况也很重要。Resource Timing API可以监控所有资源的加载时间:

function getResourceTimings() {
    const resources = performance.getEntriesByType('resource');
    
    const resourceData = resources.map(resource => ({
        name: resource.name,
        entryType: resource.entryType,
        startTime: resource.startTime,
        duration: resource.duration,
        transferSize: resource.transferSize,
        encodedBodySize: resource.encodedBodySize,
        decodedBodySize: resource.decodedBodySize,
        connectStart: resource.connectStart,
        connectEnd: resource.connectEnd,
        responseStart: resource.responseStart,
        responseEnd: resource.responseEnd
    }));
    
    return resourceData;
}

// 定期收集资源加载数据
setInterval(() => {
    const resources = getResourceTimings();
    // 只上报大文件或者加载时间长的资源
    const slowResources = resources.filter(r => r.duration > 1000 || r.transferSize > 1024 * 100);
    if (slowResources.length) {
        console.log('慢资源:', slowResources);
    }
}, 5000);

移动端的一些特殊考虑

移动端网络环境复杂,单纯记录加载时间意义不大。我在移动端增加了网络类型判断:

function getNetworkInfo() {
    const connection = navigator.connection || 
                      navigator.mozConnection || 
                      navigator.webkitConnection;
    
    if (connection) {
        return {
            effectiveType: connection.effectiveType, // 4g, 3g, 2g, slow-2g
            downlink: connection.downlink, // Mbps
            rtt: connection.rtt, // ms
            saveData: connection.saveData // 是否开启了节省数据模式
        };
    }
    return null;
}

// 结合网络信息上报
sendToMonitor(data) {
    const networkInfo = getNetworkInfo();
    
    fetch('https://jztheme.com/api/performance', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            url: window.location.href,
            timestamp: Date.now(),
            networkInfo,
            ...data
        })
    });
}

数据可视化怎么做

光收集数据没用,得能看出来趋势变化。我一般会在后台做个简单的图表展示,重点监控FP(First Paint)、FCP(First Contentful Paint)、LCP(Largest Contentful Paint)这些关键指标。

// 获取现代性能指标
function getModernMetrics() {
    const observer = new PerformanceObserver((list) => {
        list.getEntries().forEach(entry => {
            if (entry.entryType === 'paint') {
                console.log(${entry.name}: ${entry.startTime}ms);
            } else if (entry.entryType === 'largest-contentful-paint') {
                console.log(LCP: ${entry.startTime}ms);
            }
        });
    });
    
    observer.observe({ entryTypes: ['paint', 'largest-contentful-paint'] });
}

这套监控系统上线后,我们团队明显感觉到了页面性能的变化,特别是在新功能上线前后的对比上,数据说话比什么都有说服力。

以上是我踩坑后的总结,希望对你有帮助。这个技巧的拓展用法还有很多,比如结合错误监控、用户行为分析等等,后续会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论