Π§ΡΠΎ ΡΠ°ΠΊΠΎΠ΅ Performance API Π² Π±ΡΠ°ΡΠ·Π΅ΡΠ΅?
ΠΠΎΠ»Π½ΡΠΉ ΠΎΡΠ²Π΅Ρ
performance.now() Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ Π²ΡΠ΅ΠΌΡ Π² ΠΌΠΈΠ»Π»ΠΈΡΠ΅ΠΊΡΠ½Π΄Π°Ρ
Ρ ΠΌΠΎΠΌΠ΅Π½ΡΠ° Π½Π°ΡΠ°Π»Π° Π½Π°Π²ΠΈΠ³Π°ΡΠΈΠΈ:
// ΠΠ°Π·ΠΎΠ²ΠΎΠ΅ ΠΈΠ·ΠΌΠ΅ΡΠ΅Π½ΠΈΠ΅ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ
const start = performance.now();
// ΠΠ°ΠΊΠ°Ρ-ΡΠΎ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΡ
const data = heavyComputation();
const end = performance.now();
console.log(`ΠΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅: ${(end - start).toFixed(2)} ms`);
// ΠΠΎΡΠ΅ΠΌΡ Π½Π΅ Date.now()?
// Date.now() Π·Π°Π²ΠΈΡΠΈΡ ΠΎΡ ΡΠΈΡΡΠ΅ΠΌΠ½ΡΡ
ΡΠ°ΡΠΎΠ² -- ΠΎΠ½ΠΈ ΠΌΠΎΠ³ΡΡ "ΠΏΡΡΠ³Π½ΡΡΡ"
// performance.now() ΠΌΠΎΠ½ΠΎΡΠΎΠ½Π½ΠΎ ΡΠ°ΡΡΡΡ, Π½Π΅ Π·Π°Π²ΠΈΡΠΈΡ ΠΎΡ ΡΠ°ΡΠΎΠ²
User Timing API -- mark() ΠΈ measure():
// mark() ΡΡΠ°Π²ΠΈΡ ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½Π½ΡΡ ΠΌΠ΅ΡΠΊΡ Ρ timestamp
performance.mark('render-start');
renderApp();
performance.mark('render-end');
// measure() Π²ΡΡΠΈΡΠ»ΡΠ΅Ρ ΡΠ°Π·Π½ΠΈΡΡ ΠΌΠ΅ΠΆΠ΄Ρ ΠΌΠ΅ΡΠΊΠ°ΠΌΠΈ
performance.measure('render-duration', 'render-start', 'render-end');
const measures = performance.getEntriesByName('render-duration');
console.log(`Π Π΅Π½Π΄Π΅Ρ Π·Π°Π½ΡΠ»: ${measures[0].duration.toFixed(2)} ms`);
// ΠΠ΅ΡΠΊΠΈ Π²ΠΈΠ΄Π½Ρ Π² DevTools (Π²ΠΊΠ»Π°Π΄ΠΊΠ° Performance, ΡΠ°Π·Π΄Π΅Π» Timings)
PerformanceObserver -- Π½Π°Π±Π»ΡΠ΄Π΅Π½ΠΈΠ΅ Π·Π° Π·Π°ΠΏΠΈΡΡΠΌΠΈ:
// ΠΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ Π²ΡΠ΅Ρ
ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΡ
Π·Π°ΠΌΠ΅ΡΠΎΠ²
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.duration.toFixed(2)} ms`);
}
});
observer.observe({ entryTypes: ['measure'] });
// ΠΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΡΠ΅ΡΡΡΡΠΎΠ²
const resourceObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.transferSize > 100_000) {
console.warn(
`Π’ΡΠΆΡΠ»ΡΠΉ ΡΠ΅ΡΡΡΡ: ${entry.name} (${(entry.transferSize / 1024).toFixed(0)} KB)`
);
}
}
});
resourceObserver.observe({ type: 'resource', buffered: true });
ΠΠΎΠ»Π½ΡΠΉ Π½Π°Π±ΠΎΡ Π΄ΠΎΡΡΡΠΏΠ½ΡΡ entry types:
// Π’ΠΈΠΏΡ Π·Π°ΠΏΠΈΡΠ΅ΠΉ, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°Π±Π»ΡΠ΄Π°ΡΡ:
// 'navigation' -- ΡΠ°ΠΉΠΌΠΈΠ½Π³ Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΡΡΡΠ°Π½ΠΈΡΡ
// 'resource' -- ΡΠ°ΠΉΠΌΠΈΠ½Π³ Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΡΠ΅ΡΡΡΡΠ°
// 'paint' -- FP, FCP
// 'largest-contentful-paint' -- LCP
// 'first-input' -- FID
// 'layout-shift' -- CLS
// 'longtask' -- Π·Π°Π΄Π°ΡΠΈ Π΄Π»ΠΈΠ½Π½Π΅Π΅ 50 ΠΌΡ
// 'event' -- INP (Π·Π°Π΄Π΅ΡΠΆΠΊΠ° ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΡΠΎΠ±ΡΡΠΈΠΉ)
// 'mark' -- ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΠ΅ ΠΌΠ΅ΡΠΊΠΈ
// 'measure' -- ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΠ΅ Π·Π°ΠΌΠ΅ΡΡ
// ΠΡΠΎΠ²Π΅ΡΠΊΠ° ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠΈ
const supported = PerformanceObserver.supportedEntryTypes;
console.log(supported);
// ['element', 'event', 'first-input', 'largest-contentful-paint',
// 'layout-shift', 'longtask', 'mark', 'measure', 'navigation',
// 'paint', 'resource', 'visibility-state']
π Π Π΅Π°Π»ΡΠ½ΡΠ΅ ΠΊΠ΅ΠΉΡΡ
ΠΠ·ΠΌΠ΅ΡΠ΅Π½ΠΈΠ΅ ΡΠ΅Π°Π»ΡΠ½ΠΎΠΉ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ²:
function measureComponent(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).pop();
if (entry && entry.duration > 16) { // Π±ΠΎΠ»ΡΡΠ΅ ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΊΠ°Π΄ΡΠ° (60fps)
console.warn(`${name} Π·Π°Π½ΡΠ» ${entry.duration.toFixed(1)} ms -- ΡΠΎΡΠΌΠΎΠ·ΠΈΡ ΡΠ΅Π½Π΄Π΅Ρ`);
}
// ΠΡΠΈΡΡΠΊΠ°, ΡΡΠΎΠ±Ρ Π½Π΅ ΠΊΠΎΠΏΠΈΠ»ΠΈΡΡ Π·Π°ΠΏΠΈΡΠΈ
performance.clearMarks(`${name}-start`);
performance.clearMarks(`${name}-end`);
performance.clearMeasures(name);
return result;
}
measureComponent('product-list-render', () => {
return renderProductList(products);
});
ΠΡΠΏΡΠ°Π²ΠΊΠ° ΠΌΠ΅ΡΡΠΈΠΊ Π² Π°Π½Π°Π»ΠΈΡΠΈΠΊΡ:
// Π‘ΠΎΠ±ΠΈΡΠ°Π΅ΠΌ ΠΊΠ»ΡΡΠ΅Π²ΡΠ΅ ΠΌΠ΅ΡΡΠΈΠΊΠΈ ΠΏΡΠΈ Π·Π°Π³ΡΡΠ·ΠΊΠ΅ ΡΡΡΠ°Π½ΠΈΡΡ
window.addEventListener('load', () => {
// setTimeout ΡΡΠΎΠ±Ρ ΠΌΠ΅ΡΡΠΈΠΊΠΈ ΡΡΠΏΠ΅Π»ΠΈ Π·Π°ΠΏΠΈΡΠ°ΡΡΡΡ
setTimeout(() => {
const navigation = performance.getEntriesByType('navigation')[0];
const paint = performance.getEntriesByName('first-contentful-paint')[0];
const metrics = {
dns: navigation.domainLookupEnd - navigation.domainLookupStart,
tcp: navigation.connectEnd - navigation.connectStart,
ttfb: navigation.responseStart - navigation.requestStart,
domReady: navigation.domContentLoadedEventEnd - navigation.startTime,
fullLoad: navigation.loadEventEnd - navigation.startTime,
fcp: paint ? paint.startTime : null,
resourceCount: performance.getEntriesByType('resource').length,
};
navigator.sendBeacon('/api/metrics', JSON.stringify(metrics));
}, 0);
});
Π Π΅Π·ΡΠΌΠ΅
Performance API -- Π΅Π΄ΠΈΠ½ΡΡΠ²Π΅Π½Π½ΡΠΉ Π½Π°Π΄ΡΠΆΠ½ΡΠΉ ΡΠΏΠΎΡΠΎΠ± ΠΈΠ·ΠΌΠ΅ΡΡΡΡ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΡ Π² Π±ΡΠ°ΡΠ·Π΅ΡΠ΅. performance.now() ΡΠΎΡΠ½Π΅Π΅ Date.now(), mark()/measure() ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡΡ Π·Π°ΠΌΠ΅ΡΡΡΡ Π»ΡΠ±ΡΠ΅ ΡΡΠ°ΡΡΠΊΠΈ ΠΊΠΎΠ΄Π°, Π° PerformanceObserver ΠΎΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π΅Ρ ΠΌΠ΅ΡΡΠΈΠΊΠΈ Π² ΡΠ΅Π°Π»ΡΠ½ΠΎΠΌ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ. ΠΡΠ΅ Π΄Π°Π½Π½ΡΠ΅ Π²ΠΈΠ΄Π½Ρ Π² DevTools Π½Π° Π²ΠΊΠ»Π°Π΄ΠΊΠ΅ Performance.