Оптимизировать то, что не измерено — значит гадать. Performance API даёт точные инструменты для измерения времени выполнения кода, загрузки ресурсов и взаимодействия пользователя с интерфейсом.
Высокоточный таймер с разрешением до микросекунд. Отсчитывает время от момента загрузки страницы (не от 1970 года как Date.now()):
const start = performance.now()
// ... что-то выполняется ...
const end = performance.now()
console.log(`Выполнено за ${end - start} мс`)
// Например: 1.2300000190734863 мсПреимущество перед Date.now(): не зависит от системных часов и не изменяется при смене времени.
// Ставим метки
performance.mark('parse-start')
parseHugeJSON(data)
performance.mark('parse-end')
// Измеряем промежуток между метками
performance.measure('json-parsing', 'parse-start', 'parse-end')
// Получаем результат
const [measure] = performance.getEntriesByName('json-parsing')
console.log(`Парсинг JSON: ${measure.duration.toFixed(2)} мс`)
// Очистка
performance.clearMarks()
performance.clearMeasures()Подписка на появление новых записей производительности:
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log(`${entry.name}: ${entry.duration.toFixed(2)} мс`)
})
})
// Наблюдаем за пользовательскими метриками
observer.observe({ type: 'measure', buffered: true })
// Наблюдаем за LCP
observer.observe({ type: 'largest-contentful-paint', buffered: true })LCP (Largest Contentful Paint):
new PerformanceObserver((list) => {
const entries = list.getEntries()
const lcp = entries[entries.length - 1]
console.log('LCP:', lcp.startTime.toFixed(0), 'мс')
}).observe({ type: 'largest-contentful-paint', buffered: true })FID/INP:
new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log('Задержка взаимодействия:', entry.processingStart - entry.startTime, 'мс')
})
}).observe({ type: 'event', durationThreshold: 16 })CLS:
let cumulativeCLS = 0
new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (!entry.hadRecentInput) cumulativeCLS += entry.value
})
console.log('CLS:', cumulativeCLS.toFixed(4))
}).observe({ type: 'layout-shift', buffered: true })Детальная хронология загрузки страницы:
const nav = performance.getEntriesByType('navigation')[0]
console.log('DNS:', nav.domainLookupEnd - nav.domainLookupStart, 'мс')
console.log('TCP:', nav.connectEnd - nav.connectStart, 'мс')
console.log('TTFB:', nav.responseStart - nav.requestStart, 'мс')
console.log('DOM parsing:', nav.domInteractive - nav.responseEnd, 'мс')
console.log('Полная загрузка:', nav.loadEventEnd - nav.navigationStart, 'мс')Тайминги для каждого загруженного ресурса:
const resources = performance.getEntriesByType('resource')
resources.forEach(res => {
console.log(`${res.name}: ${res.duration.toFixed(0)} мс (${res.transferSize} байт)`)
})function percentile(arr, p) {
const sorted = [...arr].sort((a, b) => a - b)
const index = Math.ceil(sorted.length * p / 100) - 1
return sorted[Math.max(0, index)]
}
const durations = [12, 45, 8, 234, 15, 90, 11, 67]
console.log('P50:', percentile(durations, 50)) // медиана
console.log('P95:', percentile(durations, 95)) // 95-й перцентиль
console.log('P99:', percentile(durations, 99)) // 99-й перцентильСобирай метрики → анализируй → оптимизируй → измеряй снова. Отправляй данные на сервер через navigator.sendBeacon() (не блокирует закрытие страницы):
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
const metrics = JSON.stringify(collectMetrics())
navigator.sendBeacon('/analytics', metrics)
}
})Система измерения производительности с метками, мерами и статистическим анализом
// Полная система Performance Tracking без браузерных API
class PerformanceTracker {
constructor() {
this._marks = new Map() // name → timestamp
this._measurements = new Map() // name → number[]
}
// Аналог performance.now()
_now() { return Date.now() }
// Ставим метку времени
mark(name) {
this._marks.set(name, this._now())
console.log(`[Mark] ${name} @ ${this._marks.get(name)} мс`)
}
// Измеряем промежуток между двумя метками
measure(name, startMark, endMark) {
const start = this._marks.get(startMark)
const end = this._marks.get(endMark)
if (start === undefined) throw new Error(`Метка не найдена: ${startMark}`)
if (end === undefined) throw new Error(`Метка не найдена: ${endMark}`)
const duration = end - start
if (!this._measurements.has(name)) {
this._measurements.set(name, [])
}
this._measurements.get(name).push(duration)
console.log(`[Measure] ${name}: ${duration} мс`)
return duration
}
// Вычисление p95
_percentile(arr, p) {
const sorted = [...arr].sort((a, b) => a - b)
const index = Math.ceil(sorted.length * p / 100) - 1
return sorted[Math.max(0, index)]
}
// Полная статистика по всем измерениям
getMetrics() {
const result = []
for (const [name, durations] of this._measurements) {
const count = durations.length
const sum = durations.reduce((a, b) => a + b, 0)
const avg = sum / count
const min = Math.min(...durations)
const max = Math.max(...durations)
const p95 = this._percentile(durations, 95)
result.push({ name, duration: durations[durations.length - 1], count, avg, min, max, p95 })
}
return result
}
// Удобная обёртка для измерения функции
async time(name, fn) {
const startMark = `${name}-start-${Date.now()}`
const endMark = `${name}-end-${Date.now()}`
this.mark(startMark)
const result = await fn()
this.mark(endMark)
this.measure(name, startMark, endMark)
return result
}
// Красивый отчёт
report() {
console.log('\n=== Performance Report ===')
const metrics = this.getMetrics()
metrics.forEach(m => {
console.log(`${m.name}:`)
console.log(` count=${m.count}, avg=${m.avg.toFixed(1)}мс, min=${m.min}мс, max=${m.max}мс, p95=${m.p95}мс`)
})
}
}
// Демо: измеряем разные операции
const tracker = new PerformanceTracker()
// Симулируем операции с разным временем
async function simulateOperation(minMs, maxMs) {
const delay = minMs + Math.random() * (maxMs - minMs)
await new Promise(r => setTimeout(r, Math.round(delay)))
}
// Несколько итераций для статистики
console.log('Измеряем производительность...')
for (let i = 0; i < 5; i++) {
tracker.mark(`api-start-${i}`)
await simulateOperation(10, 50)
tracker.mark(`api-end-${i}`)
tracker.measure('api-call', `api-start-${i}`, `api-end-${i}`)
}
for (let i = 0; i < 3; i++) {
tracker.mark(`render-start-${i}`)
await simulateOperation(5, 20)
tracker.mark(`render-end-${i}`)
tracker.measure('render', `render-start-${i}`, `render-end-${i}`)
}
tracker.report()Оптимизировать то, что не измерено — значит гадать. Performance API даёт точные инструменты для измерения времени выполнения кода, загрузки ресурсов и взаимодействия пользователя с интерфейсом.
Высокоточный таймер с разрешением до микросекунд. Отсчитывает время от момента загрузки страницы (не от 1970 года как Date.now()):
const start = performance.now()
// ... что-то выполняется ...
const end = performance.now()
console.log(`Выполнено за ${end - start} мс`)
// Например: 1.2300000190734863 мсПреимущество перед Date.now(): не зависит от системных часов и не изменяется при смене времени.
// Ставим метки
performance.mark('parse-start')
parseHugeJSON(data)
performance.mark('parse-end')
// Измеряем промежуток между метками
performance.measure('json-parsing', 'parse-start', 'parse-end')
// Получаем результат
const [measure] = performance.getEntriesByName('json-parsing')
console.log(`Парсинг JSON: ${measure.duration.toFixed(2)} мс`)
// Очистка
performance.clearMarks()
performance.clearMeasures()Подписка на появление новых записей производительности:
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log(`${entry.name}: ${entry.duration.toFixed(2)} мс`)
})
})
// Наблюдаем за пользовательскими метриками
observer.observe({ type: 'measure', buffered: true })
// Наблюдаем за LCP
observer.observe({ type: 'largest-contentful-paint', buffered: true })LCP (Largest Contentful Paint):
new PerformanceObserver((list) => {
const entries = list.getEntries()
const lcp = entries[entries.length - 1]
console.log('LCP:', lcp.startTime.toFixed(0), 'мс')
}).observe({ type: 'largest-contentful-paint', buffered: true })FID/INP:
new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
console.log('Задержка взаимодействия:', entry.processingStart - entry.startTime, 'мс')
})
}).observe({ type: 'event', durationThreshold: 16 })CLS:
let cumulativeCLS = 0
new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (!entry.hadRecentInput) cumulativeCLS += entry.value
})
console.log('CLS:', cumulativeCLS.toFixed(4))
}).observe({ type: 'layout-shift', buffered: true })Детальная хронология загрузки страницы:
const nav = performance.getEntriesByType('navigation')[0]
console.log('DNS:', nav.domainLookupEnd - nav.domainLookupStart, 'мс')
console.log('TCP:', nav.connectEnd - nav.connectStart, 'мс')
console.log('TTFB:', nav.responseStart - nav.requestStart, 'мс')
console.log('DOM parsing:', nav.domInteractive - nav.responseEnd, 'мс')
console.log('Полная загрузка:', nav.loadEventEnd - nav.navigationStart, 'мс')Тайминги для каждого загруженного ресурса:
const resources = performance.getEntriesByType('resource')
resources.forEach(res => {
console.log(`${res.name}: ${res.duration.toFixed(0)} мс (${res.transferSize} байт)`)
})function percentile(arr, p) {
const sorted = [...arr].sort((a, b) => a - b)
const index = Math.ceil(sorted.length * p / 100) - 1
return sorted[Math.max(0, index)]
}
const durations = [12, 45, 8, 234, 15, 90, 11, 67]
console.log('P50:', percentile(durations, 50)) // медиана
console.log('P95:', percentile(durations, 95)) // 95-й перцентиль
console.log('P99:', percentile(durations, 99)) // 99-й перцентильСобирай метрики → анализируй → оптимизируй → измеряй снова. Отправляй данные на сервер через navigator.sendBeacon() (не блокирует закрытие страницы):
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
const metrics = JSON.stringify(collectMetrics())
navigator.sendBeacon('/analytics', metrics)
}
})Система измерения производительности с метками, мерами и статистическим анализом
// Полная система Performance Tracking без браузерных API
class PerformanceTracker {
constructor() {
this._marks = new Map() // name → timestamp
this._measurements = new Map() // name → number[]
}
// Аналог performance.now()
_now() { return Date.now() }
// Ставим метку времени
mark(name) {
this._marks.set(name, this._now())
console.log(`[Mark] ${name} @ ${this._marks.get(name)} мс`)
}
// Измеряем промежуток между двумя метками
measure(name, startMark, endMark) {
const start = this._marks.get(startMark)
const end = this._marks.get(endMark)
if (start === undefined) throw new Error(`Метка не найдена: ${startMark}`)
if (end === undefined) throw new Error(`Метка не найдена: ${endMark}`)
const duration = end - start
if (!this._measurements.has(name)) {
this._measurements.set(name, [])
}
this._measurements.get(name).push(duration)
console.log(`[Measure] ${name}: ${duration} мс`)
return duration
}
// Вычисление p95
_percentile(arr, p) {
const sorted = [...arr].sort((a, b) => a - b)
const index = Math.ceil(sorted.length * p / 100) - 1
return sorted[Math.max(0, index)]
}
// Полная статистика по всем измерениям
getMetrics() {
const result = []
for (const [name, durations] of this._measurements) {
const count = durations.length
const sum = durations.reduce((a, b) => a + b, 0)
const avg = sum / count
const min = Math.min(...durations)
const max = Math.max(...durations)
const p95 = this._percentile(durations, 95)
result.push({ name, duration: durations[durations.length - 1], count, avg, min, max, p95 })
}
return result
}
// Удобная обёртка для измерения функции
async time(name, fn) {
const startMark = `${name}-start-${Date.now()}`
const endMark = `${name}-end-${Date.now()}`
this.mark(startMark)
const result = await fn()
this.mark(endMark)
this.measure(name, startMark, endMark)
return result
}
// Красивый отчёт
report() {
console.log('\n=== Performance Report ===')
const metrics = this.getMetrics()
metrics.forEach(m => {
console.log(`${m.name}:`)
console.log(` count=${m.count}, avg=${m.avg.toFixed(1)}мс, min=${m.min}мс, max=${m.max}мс, p95=${m.p95}мс`)
})
}
}
// Демо: измеряем разные операции
const tracker = new PerformanceTracker()
// Симулируем операции с разным временем
async function simulateOperation(minMs, maxMs) {
const delay = minMs + Math.random() * (maxMs - minMs)
await new Promise(r => setTimeout(r, Math.round(delay)))
}
// Несколько итераций для статистики
console.log('Измеряем производительность...')
for (let i = 0; i < 5; i++) {
tracker.mark(`api-start-${i}`)
await simulateOperation(10, 50)
tracker.mark(`api-end-${i}`)
tracker.measure('api-call', `api-start-${i}`, `api-end-${i}`)
}
for (let i = 0; i < 3; i++) {
tracker.mark(`render-start-${i}`)
await simulateOperation(5, 20)
tracker.mark(`render-end-${i}`)
tracker.measure('render', `render-start-${i}`, `render-end-${i}`)
}
tracker.report()Реализуй createPerformanceTracker() с методами: mark(name) сохраняет текущее время, measure(name, startMark, endMark) вычисляет продолжительность и сохраняет (поддерживает несколько измерений с одним именем), getMetrics() возвращает массив объектов { name, duration (последнее), count, avg, min, max, p95 } для каждого уникального имени измерения.
marks.set(name, Date.now()) сохраняет метку. В measure() duration = end - start. measurements.get(name).push(duration) накапливает значения. В getMetrics() avg = sum / count. Math.min(...durations) и Math.max(...durations) находят минимум и максимум. percentile получает индекс Math.max(0, index).