Performance API 深度實戰:Long Tasks、INP 與效能監控體系
性能优化(更新於 2026年6月20日)
Performance API 體系概覽
瀏覽器 Performance API 提供了一套完整的效能度量與監控介面,從導航計時到元素渲染、從長任務偵測到互動響應,涵蓋了效能監控的全鏈路。
| API | 監控目標 | 型別 |
|---|---|---|
PerformanceObserver |
各類效能條目的觀察器 | 核心 |
PerformanceNavigationTiming |
頁面導航與載入 | 導航 |
PerformanceResourceTiming |
資源載入耗時 | 資源 |
PerformanceLongTaskTiming |
長任務(>50ms) | 互動 |
PerformanceEventTiming |
使用者互動延遲 | 互動 |
PerformanceElementTiming |
元素渲染時間 | 渲染 |
PerformanceLayoutShift |
佈局偏移 | 穩定性 |
PerformancePaintTiming |
首次繪製/內容繪製 | 渲染 |
PerformanceObserver 基礎
核心模式
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime, entry.duration);
}
});
observer.observe({ type: 'longtask', buffered: true });
觀察多個型別
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
switch (entry.entryType) {
case 'longtask':
reportLongTask(entry);
break;
case 'event':
reportEventTiming(entry);
break;
case 'layout-shift':
reportLayoutShift(entry);
break;
case 'element':
reportElementTiming(entry);
break;
}
}
});
observer.observe({
type: ['longtask', 'event', 'layout-shift', 'element'],
buffered: true
});
Long Tasks 監控
Long Tasks 是指主執行緒上執行超過 50ms 的任務,會阻塞使用者互動並導致頁面卡頓。
基礎監控
const longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log({
name: entry.name,
startTime: entry.startTime.toFixed(2),
duration: entry.duration.toFixed(2),
culprit: entry.attribution?.[0]?.containerType,
containerName: entry.attribution?.[0]?.containerName,
});
}
});
longTaskObserver.observe({ type: 'longtask', buffered: true });
長任務歸因分析
function analyzeLongTask(entry) {
const attribution = entry.attribution?.[0];
if (!attribution) return { type: 'unknown' };
return {
type: attribution.containerType,
containerName: attribution.containerName,
containerId: attribution.containerId,
containerSrc: attribution.containerSrc,
duration: entry.duration,
startTime: entry.startTime,
};
}
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const analysis = analyzeLongTask(entry);
reportToAnalytics('longtask', analysis);
}
});
observer.observe({ type: 'longtask', buffered: true });
長任務最佳化策略
| 策略 | 說明 | 範例 |
|---|---|---|
| 任務拆分 | 將大任務拆分為 <50ms 的小任務 | scheduler.yield() |
| 延遲執行 | 非關鍵任務推遲到空閒期 | requestIdleCallback |
| Web Worker | CPU 密集型任務移至 Worker | 資料處理、排序 |
| 程式碼分割 | 按需載入減少初始 JS 體積 | 動態 import() |
| 排程優先級 | 使用 Scheduler API 控制執行順序 | scheduler.postTask |
async function yieldToMain() {
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
return new Promise((resolve) => setTimeout(resolve, 0));
}
async function processLargeList(items) {
const results = [];
for (let i = 0; i < items.length; i++) {
results.push(processItem(items[i]));
if (i % 100 === 0) await yieldToMain();
}
return results;
}
Interaction to Next Paint (INP)
INP 是 Core Web Vitals 的互動響應指標,衡量使用者與頁面互動到下一次繪製的延遲。
INP 評分標準
| 評分 | INP 值 | 使用者體驗 |
|---|---|---|
| 良好 | ≤ 200ms | 響應迅速 |
| 需改進 | 200-500ms | 可感知延遲 |
| 較差 | > 500ms | 明顯卡頓 |
監控 INP
let maxInteraction = 0;
let worstInteraction = null;
const inpObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.interactionId) continue;
const duration = entry.duration;
if (duration > maxInteraction) {
maxInteraction = duration;
worstInteraction = {
interactionId: entry.interactionId,
type: entry.name,
duration,
startTime: entry.startTime,
processingStart: entry.processingStart,
processingEnd: entry.processingEnd,
inputDelay: entry.processingStart - entry.startTime,
processingDuration: entry.processingEnd - entry.processingStart,
presentationDelay: entry.duration - (entry.processingEnd - entry.startTime),
};
}
}
});
inpObserver.observe({ type: 'event', buffered: true, durationThreshold: 16 });
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
if (worstInteraction) {
reportToAnalytics('inp', {
value: maxInteraction,
rating: maxInteraction <= 200 ? 'good' : maxInteraction <= 500 ? 'needs-improvement' : 'poor',
...worstInteraction,
});
}
}
});
INP 延遲分解
使用者互動 → 輸入延遲 → 事件處理 → 呈現延遲 → 下一幀繪製
─────────────────────────────────────────────
|← INP (duration) →|
輸入延遲:startTime → processingStart
原因:主執行緒被長任務阻塞
事件處理:processingStart → processingEnd
原因:事件回呼執行過慢
呈現延遲:processingEnd → 下一幀繪製
原因:requestAnimationFrame 回呼或樣式/佈局計算過重
Element Timing 監控
Element Timing 追蹤關鍵內容的渲染時間,適用於 LCP 元素和自訂關鍵元素。
<img src="hero.jpg" elementtiming="hero-image" />
<p elementtiming="hero-text">關鍵內容</p>
<div elementtiming="main-content">...</div>
const elementObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log({
identifier: entry.identifier,
startTime: entry.startTime,
renderTime: entry.renderTime,
loadTime: entry.loadTime,
size: entry.size,
url: entry.url,
elementType: entry.element?.tagName,
});
}
});
elementObserver.observe({ type: 'element', buffered: true });
RUM 監控體系建構
核心指標收集器
class PerformanceMonitor {
constructor(options = {}) {
this.endpoint = options.endpoint;
this.metrics = {};
this.observers = [];
}
start() {
this.collectWebVitals();
this.collectLongTasks();
this.collectResourceTiming();
this.collectNavigationTiming();
this.scheduleReport();
}
collectWebVitals() {
this.observe('event', (entry) => {
if (!entry.interactionId) return;
this.metrics.inp = Math.max(this.metrics.inp ?? 0, entry.duration);
});
this.observe('largest-contentful-paint', (entry) => {
this.metrics.lcp = entry.startTime;
});
this.observe('layout-shift', (entry) => {
if (!entry.hadRecentInput) {
this.metrics.cls = (this.metrics.cls ?? 0) + entry.value;
}
});
this.observe('first-input', (entry) => {
this.metrics.fid = entry.processingStart - entry.startTime;
});
}
collectLongTasks() {
this.observe('longtask', (entry) => {
if (!this.metrics.longTasks) this.metrics.longTasks = [];
this.metrics.longTasks.push({
duration: entry.duration,
startTime: entry.startTime,
culprit: entry.attribution?.[0]?.containerType,
});
});
}
collectResourceTiming() {
this.observe('resource', (entry) => {
if (entry.duration > 1000) {
if (!this.metrics.slowResources) this.metrics.slowResources = [];
this.metrics.slowResources.push({
name: entry.name,
duration: entry.duration,
initiatorType: entry.initiatorType,
transferSize: entry.transferSize,
});
}
});
}
collectNavigationTiming() {
const [nav] = performance.getEntriesByType('navigation');
if (nav) {
this.metrics.ttfb = nav.responseStart - nav.requestStart;
this.metrics.domContentLoaded = nav.domContentLoadedEventEnd - nav.startTime;
this.metrics.loadComplete = nav.loadEventEnd - nav.startTime;
this.metrics.domInteractive = nav.domInteractive - nav.startTime;
}
}
observe(type, callback) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) callback(entry);
});
observer.observe({ type, buffered: true });
this.observers.push(observer);
}
scheduleReport() {
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') this.report();
});
}
async report() {
const payload = {
url: location.href,
timestamp: Date.now(),
userAgent: navigator.userAgent,
connection: navigator.connection
? { effectiveType: navigator.connection.effectiveType, downlink: navigator.connection.downlink }
: null,
metrics: this.metrics,
};
if (navigator.sendBeacon) {
navigator.sendBeacon(this.endpoint, JSON.stringify(payload));
} else {
fetch(this.endpoint, { method: 'POST', body: JSON.stringify(payload), keepalive: true });
}
}
destroy() {
this.observers.forEach((o) => o.disconnect());
}
}
const monitor = new PerformanceMonitor({ endpoint: '/api/vitals' });
monitor.start();
Core Web Vitals 指標全景
| 指標 | 全稱 | 閾值(好/差) | 監控型別 | 權重 |
|---|---|---|---|---|
| LCP | Largest Contentful Paint | ≤2.5s / >4s | largest-contentful-paint |
25% |
| INP | Interaction to Next Paint | ≤200ms / >500ms | event |
25% |
| CLS | Cumulative Layout Shift | ≤0.1 / >0.25 | layout-shift |
25% |
| FCP | First Contentful Paint | ≤1.8s / >3s | paint |
10% |
| TTFB | Time to First Byte | ≤800ms / >1800ms | navigation |
15% |
效能資料上報最佳化
批次上報與壓縮
class MetricsBatcher {
constructor(endpoint, options = {}) {
this.endpoint = endpoint;
this.batchSize = options.batchSize ?? 10;
this.flushInterval = options.flushInterval ?? 5000;
this.queue = [];
this.timer = null;
}
enqueue(metric) {
this.queue.push(metric);
if (this.queue.length >= this.batchSize) this.flush();
else if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.flushInterval);
}
}
flush() {
if (this.timer) { clearTimeout(this.timer); this.timer = null; }
if (this.queue.length === 0) return;
const payload = JSON.stringify(this.queue);
this.queue = [];
if (navigator.sendBeacon) {
navigator.sendBeacon(this.endpoint, payload);
} else {
fetch(this.endpoint, { method: 'POST', body: payload, keepalive: true });
}
}
}
最佳實踐總結
- 使用
buffered: true:確保不遺漏頁面載入早期的效能條目 - INP 在
visibilitychange時上報:取會話內最差互動值 - 長任務歸因:利用
attribution定位問題來源(iframe/script) sendBeacon優先:頁面卸載時保證資料送達- 關注 INP 三段延遲:分別最佳化輸入延遲、事件處理、呈現延遲
本站提供瀏覽器本地工具,免註冊即可試用 →
#Performance API#PerformanceObserver#Long Tasks#INP#性能监控