pin_drop当前位置:知识文库 ❯ 图文

HTML5 API:HTML5 Performance Observe - 完整教程与代码示例

一、教程简介

Performance Observer API 是 Performance Timeline 的一部分,提供了监听性能条目的能力,可以异步获取页面加载、资源请求、用户交互、长任务等各类性能数据。相比轮询 performance.getEntries(),Performance Observer 采用回调机制,只在新的性能条目产生时触发,性能更优,是构建 Web 性能监控系统的核心 API。


二、核心概念

性能条目类型

类型 说明
navigation 页面导航性能
resource 资源加载性能
mark 自定义标记
measure 自定义测量
paint 绘制性能(FP/FCP)
longtask 长任务(>50ms)
element 元素可见性
event 事件处理延迟
largest-contentful-paint 最大内容绘制(LCP)
layout-shift 布局偏移(CLS)
first-input 首次输入延迟(FID)

核心 Web 指标

指标 含义 目标值
LCP 最大内容绘制 < 2.5s
FID/INP 首次输入延迟/交互延迟 < 100ms
CLS 累积布局偏移 < 0.1

三、语法与用法

创建与观察

代码示例

const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        console.log(entry.name, entry.startTime, entry.duration);
    }
});

observer.observe({ type: 'resource', buffered: true });

自定义标记与测量

代码示例

performance.mark('start-task');
// ... 执行任务
performance.mark('end-task');
performance.measure('task-duration', 'start-task', 'end-task');

const measure = performance.getEntriesByName('task-duration')[0];
console.log('任务耗时:', measure.duration, 'ms');

四、代码示例

示例一:性能监控面板

以下是一个完整的性能监控面板示例,展示了如何使用 Performance API 和 Performance Observer 监控页面性能指标:

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Performance Observer - 性能监控</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: #1a1a2e;
            min-height: 100vh;
            padding: 30px 20px;
            color: #e0e0e0;
        }
        .container { max-width: 800px; margin: 0 auto; }
        h1 { font-size: 24px; color: #fff; margin-bottom: 8px; }
        .desc { color: #7a8ba0; font-size: 14px; margin-bottom: 24px; }
        .metrics {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 16px;
            margin-bottom: 24px;
        }
        .metric-card {
            background: #16213e;
            border-radius: 12px;
            padding: 20px;
            text-align: center;
            border: 1px solid #1a3a5c;
        }
        .metric-label { font-size: 12px; color: #7a8ba0; margin-bottom: 8px; }
        .metric-value { font-size: 28px; font-weight: 700; }
        .metric-value.good { color: #00b894; }
        .metric-value.warn { color: #fdcb6e; }
        .metric-value.bad { color: #e17055; }
        .metric-unit { font-size: 14px; color: #7a8ba0; }
        .card {
            background: #16213e;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
            border: 1px solid #1a3a5c;
        }
        .card h2 { font-size: 16px; color: #fff; margin-bottom: 16px; }
        .btn-group { display: flex; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }
        button {
            padding: 8px 18px;
            border: none;
            border-radius: 6px;
            font-size: 13px;
            font-weight: 600;
            cursor: pointer;
        }
        .btn-cyan { background: #00cec9; color: #1a1a2e; }
        .btn-cyan:hover { background: #00b5ad; }
        .btn-orange { background: #fdcb6e; color: #1a1a2e; }
        .btn-orange:hover { background: #e5b85c; }
        .btn-purple { background: #a29bfe; color: #1a1a2e; }
        .btn-purple:hover { background: #8c82f7; }
        .timing-list { list-style: none; }
        .timing-item {
            display: flex;
            justify-content: space-between;
            padding: 8px 0;
            border-bottom: 1px solid #1a3a5c;
            font-size: 13px;
        }
        .timing-item:last-child { border-bottom: none; }
        .timing-label { color: #7a8ba0; }
        .timing-value { color: #00cec9; font-weight: 600; font-family: monospace; }
        .resource-table {
            width: 100%;
            border-collapse: collapse;
        }
        .resource-table th, .resource-table td {
            padding: 8px 10px;
            text-align: left;
            border-bottom: 1px solid #1a3a5c;
            font-size: 12px;
        }
        .resource-table th { color: #7a8ba0; font-weight: 500; }
        .resource-table td { color: #dfe6e9; }
        .custom-timer {
            display: flex;
            gap: 12px;
            align-items: center;
            margin-bottom: 12px;
        }
        .timer-display {
            font-size: 24px;
            font-weight: 700;
            color: #00cec9;
            font-family: monospace;
            min-width: 100px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>性能监控面板</h1>
        <p class="desc">使用 Performance API 和 Performance Observer 监控页面性能指标。</p>

        <div class="metrics">
            <div class="metric-card">
                <div class="metric-label">页面加载</div>
                <div class="metric-value good" id="loadTime">--</div>
                <div class="metric-unit">ms</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">DOM 就绪</div>
                <div class="metric-value good" id="domReady">--</div>
                <div class="metric-unit">ms</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">资源数量</div>
                <div class="metric-value" id="resourceCount" style="color:#74b9ff;">0</div>
                <div class="metric-unit">个</div>
            </div>
        </div>

        <div class="card">
            <h2>自定义计时器</h2>
            <div class="custom-timer">
                <button class="btn-cyan" onclick="startTimer()">开始</button>
                <button class="btn-orange" onclick="stopTimer()">停止</button>
                <button class="btn-purple" onclick="measureTimer()">测量</button>
                <span class="timer-display" id="timerDisplay">0.00 ms</span>
            </div>
            <ul class="timing-list" id="customMeasures"></ul>
        </div>

        <div class="card">
            <h2>导航时序</h2>
            <ul class="timing-list" id="navTiming"></ul>
        </div>

        <div class="card">
            <h2>资源加载</h2>
            <div style="max-height:300px;overflow-y:auto;">
                <table class="resource-table">
                    <thead><tr><th>资源</th><th>类型</th><th>耗时</th><th>大小</th></tr></thead>
                    <tbody id="resourceList"></tbody>
                </table>
            </div>
        </div>
    </div>

    <script>
        // 导航时序
        function renderNavTiming() {
            const nav = performance.getEntriesByType('navigation')[0];
            if (!nav) return;

            const timings = [
                { label: 'DNS 查询', value: nav.domainLookupEnd - nav.domainLookupStart },
                { label: 'TCP 连接', value: nav.connectEnd - nav.connectStart },
                { label: '请求发送', value: nav.responseStart - nav.requestStart },
                { label: '响应接收', value: nav.responseEnd - nav.responseStart },
                { label: 'DOM 解析', value: nav.domInteractive - nav.responseEnd },
                { label: 'DOM 完成', value: nav.domComplete - nav.domInteractive },
                { label: '总加载时间', value: nav.loadEventEnd - nav.startTime }
            ];

            document.getElementById('loadTime').textContent = Math.round(nav.loadEventEnd - nav.startTime);
            document.getElementById('domReady').textContent = Math.round(nav.domInteractive - nav.startTime);

            document.getElementById('navTiming').innerHTML = timings.map(function (t) {
                const cls = t.value < 1000 ? 'good' : t.value < 3000 ? 'warn' : 'bad';
                return '<li class="timing-item"><span class="timing-label">' + t.label +
                    '</span><span class="timing-value">' + Math.round(t.value) + ' ms</span></li>';
            }).join('');
        }

        // 资源加载
        function renderResources() {
            const resources = performance.getEntriesByType('resource');
            document.getElementById('resourceCount').textContent = resources.length;

            document.getElementById('resourceList').innerHTML = resources.slice(0, 20).map(function (r) {
                const name = r.name.split('/').pop().substring(0, 30);
                const type = r.initiatorType || 'unknown';
                const duration = Math.round(r.duration);
                const size = r.transferSize > 0 ? (r.transferSize / 1024).toFixed(1) + ' KB' : '-';
                return '<tr><td>' + name + '</td><td>' + type + '</td><td>' + duration + 'ms</td><td>' + size + '</td></tr>';
            }).join('');
        }

        // 自定义计时器
        let timerMark = null;

        function startTimer() {
            performance.mark('timer-start');
            timerMark = Date.now();
            document.getElementById('timerDisplay').textContent = '运行中...';
        }

        function stopTimer() {
            if (!timerMark) return;
            performance.mark('timer-end');
            const elapsed = Date.now() - timerMark;
            document.getElementById('timerDisplay').textContent = elapsed.toFixed(2) + ' ms';
        }

        function measureTimer() {
            if (!timerMark) return;
            stopTimer();

            const id = 'measure-' + Date.now();
            performance.measure(id, 'timer-start', 'timer-end');

            const entries = performance.getEntriesByName(id);
            if (entries.length > 0) {
                const duration = entries[0].duration.toFixed(2);
                const list = document.getElementById('customMeasures');
                const item = document.createElement('li');
                item.className = 'timing-item';
                item.innerHTML = '<span class="timing-label">' + id + '</span><span class="timing-value">' + duration + ' ms</span>';
                list.insertBefore(item, list.firstChild);
            }

            timerMark = null;
            document.getElementById('timerDisplay').textContent = '0.00 ms';
        }

        // Performance Observer 监听新资源
        if (typeof PerformanceObserver !== 'undefined') {
            try {
                const resObserver = new PerformanceObserver(function (list) {
                    const entries = list.getEntries();
                    document.getElementById('resourceCount').textContent =
                        performance.getEntriesByType('resource').length;
                });
                resObserver.observe({ type: 'resource', buffered: true });
            } catch (e) {}

            // 监听长任务
            try {
                const ltObserver = new PerformanceObserver(function (list) {
                    for (const entry of list.getEntries()) {
                        console.warn('长任务检测:', entry.duration.toFixed(0) + 'ms', entry.name);
                    }
                });
                ltObserver.observe({ type: 'longtask', buffered: true });
            } catch (e) {}
        }

        // 初始化
        window.addEventListener('load', function () {
            setTimeout(function () {
                renderNavTiming();
                renderResources();
            }, 100);
        });
    </script>
</body>
</html>

五、浏览器兼容性

浏览器 PerformanceObserver buffered 选项
Chrome 52+ 73+
Firefox 57+ 77+
Safari 11+ 14.1+
Edge 79+ 79+
IE 不支持 不支持

六、注意事项与最佳实践

1. 使用 buffered 获取历史条目

代码示例

observer.observe({ type: 'resource', buffered: true });
// buffered: true 可以获取观察之前已产生的条目

2. 处理不支持的类型

代码示例

try {
    const observer = new PerformanceObserver(callback);
    observer.observe({ type: 'longtask', buffered: true });
} catch (e) {
    console.warn('longtask 类型不支持');
}

3. 及时断开观察

代码示例

observer.disconnect();

七、代码规范示例

代码示例

class PerformanceMonitor {
    constructor() {
        this.observers = [];
        this.metrics = {};
    }

    observe(types, callback) {
        types.forEach(type => {
            try {
                const observer = new PerformanceObserver((list) => {
                    list.getEntries().forEach(entry => callback(entry));
                });
                observer.observe({ type, buffered: true });
                this.observers.push(observer);
            } catch (e) {
                console.warn('Performance type not supported:', type);
            }
        });
    }

    getNavigationTiming() {
        const nav = performance.getEntriesByType('navigation')[0];
        return nav ? {
            dns: nav.domainLookupEnd - nav.domainLookupStart,
            tcp: nav.connectEnd - nav.connectStart,
            ttfb: nav.responseStart - nav.requestStart,
            domReady: nav.domInteractive - nav.startTime,
            load: nav.loadEventEnd - nav.startTime
        } : null;
    }

    measure(name, fn) {
        performance.mark(name + '-start');
        const result = fn();
        performance.mark(name + '-end');
        performance.measure(name, name + '-start', name + '-end');
        const entry = performance.getEntriesByName(name)[0];
        return { result, duration: entry?.duration };
    }

    async measureAsync(name, fn) {
        performance.mark(name + '-start');
        const result = await fn();
        performance.mark(name + '-end');
        performance.measure(name, name + '-start', name + '-end');
        const entry = performance.getEntriesByName(name)[0];
        return { result, duration: entry?.duration };
    }

    destroy() {
        this.observers.forEach(o => o.disconnect());
        this.observers = [];
    }
}

八、常见问题与解决方案

常见问题

Q1:PerformanceObserver 回调不触发?

原因:可能是类型名称错误或浏览器不支持该类型。

解决方案:使用 try-catch 包裹,检查类型名称是否正确。

Q2:如何获取 Core Web Vitals?

可以通过 Performance Observer 监听特定类型来获取 Core Web Vitals:

代码示例

// LCP
new PerformanceObserver(list => {
    const entries = list.getEntries();
    console.log('LCP:', entries[entries.length - 1].startTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });

// CLS
let clsValue = 0;
new PerformanceObserver(list => {
    list.getEntries().forEach(entry => {
        if (!entry.hadRecentInput) clsValue += entry.value;
    });
    console.log('CLS:', clsValue);
}).observe({ type: 'layout-shift', buffered: true });

// FID
new PerformanceObserver(list => {
    list.getEntries().forEach(entry => {
        console.log('FID:', entry.processingStart - entry.startTime);
    });
}).observe({ type: 'first-input', buffered: true });
Q3:如何清理自定义标记?

可以使用以下方法清理自定义标记:

代码示例

performance.clearMarks();
performance.clearMeasures();
performance.clearResourceTimings();

九、总结

Performance Observer API 提供了异步监听性能条目的能力,可以高效地获取导航时序、资源加载、自定义测量、长任务等性能数据。通过 buffered: true 可以获取历史条目,配合自定义 mark/measure 可以精确测量代码执行时间。该 API 是构建 Web 性能监控系统的基础,对于优化页面加载速度和用户体验至关重要。

标签: PerformanceObserver 性能监控 CoreWebVitals Web性能优化 性能测量

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: HTML5 API:HTML5 Resize Observer - 完整教程与代码示例 下一篇: HTML5 API:HTML5 Fetch API - 完整教程与代码示例

poll相关推荐