Skip to content
24 / 36

Π§Ρ‚ΠΎ Ρ‚Π°ΠΊΠΎΠ΅ Performance API Π² Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π΅?


ΠŸΠΎΠ»Π½Ρ‹ΠΉ ΠΎΡ‚Π²Π΅Ρ‚

performance.now() Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ врСмя Π² миллисСкундах с ΠΌΠΎΠΌΠ΅Π½Ρ‚Π° Π½Π°Ρ‡Π°Π»Π° Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ:

JavaScript
// Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ ΠΈΠ·ΠΌΠ΅Ρ€Π΅Π½ΠΈΠ΅ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ выполнСния
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():

JavaScript
// 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 -- наблюдСниС за записями:

JavaScript
// ΠžΡ‚ΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ всСх ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΡ… Π·Π°ΠΌΠ΅Ρ€ΠΎΠ²
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:

JavaScript
// Π’ΠΈΠΏΡ‹ записСй, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°Π±Π»ΡŽΠ΄Π°Ρ‚ΡŒ:
// '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']

πŸ“Œ Π Π΅Π°Π»ΡŒΠ½Ρ‹Π΅ кСйсы

Π˜Π·ΠΌΠ΅Ρ€Π΅Π½ΠΈΠ΅ Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΉ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ²:

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

ΠžΡ‚ΠΏΡ€Π°Π²ΠΊΠ° ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊ Π² Π°Π½Π°Π»ΠΈΡ‚ΠΈΠΊΡƒ:

JavaScript
// Π‘ΠΎΠ±ΠΈΡ€Π°Π΅ΠΌ ΠΊΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ ΠΌΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ страницы
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.

Π§Ρ‚ΠΎ Ρ‚Π°ΠΊΠΎΠ΅ Performance API Π² Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π΅? | JScriptiser