Производительность – один из важнейших показателей работы сайта. У вас может быть лучший сайт в мире, но если он загружается 2 минуты, никто его не увидит – никто не станет дожидаться, пока он откроется.
Однако если ваш сайт загружается 2 минуты, найти и устранить причину не составит труда. Гораздо сложнее оптимизировать сайт, среднее время загрузки которого – 1 секунда, а вы хотите сократить его до 0,85 секунды.
Существует множество инструментов, которые могут помочь вам понять, как приложение работает локально. Performance API предоставляет детальное представление о работе веб-страниц в среде производства. Вы можете получить реальные данные и увидеть, как ваш сайт работает в разных браузерах, сетях и даже в разных частях мира!
Performance API часто называют составным. Он содержит в себе слишком много всего, чтобы описать это всё в одной статье. В этом руководстве мы рассмотрим самые основные функции, которые помогут вам начать мониторинг производительности.
API развивается, и в будущем в нем появится много новых функций, а другие функции, в свою очередь, устареют. Все составные интерфейсы Performance API приближаются к уровню 2; часть из них уже реализована, часть пока остается в разработке. Поэтому рекомендуем регулярно проверять сайт MDN или W3C на наличие свежих обновлений.
Читайте также: Получение данных с помощью JavaScript Fetch API
Как получить доступ к данным о производительности
Метод performance.now
Самый простой способ измерить производительность программы — использовать performance.now(). Этот метод вернет текущее время в миллисекундах. Если вы хотите изучить время в высоком разрешении, настоятельно рекомендуем прочитать материал редактора W3C по этой теме.
performance.now позволяет вам измерять только то, что находится в вашем коде JavaScript (то есть производительность пользователя). Позже в этом посте мы рассмотрим пример использования performance.now.
Для доступа к различным событиям DOM и браузера у нас есть 3 функции:
getEntries() возвращает все доступные записи о производительности. Попробуйте запустить performance.getEntries() на текущей странице, и вы увидите большой массив. Изначально большинство записей будут относиться ко всем изображениям, сценариям и другим вещам, которые загружаются страницей (то есть к ресурсам).
const tenthEntry = performance.getEntries()[10]
// on Alligator.io it will return the following object
// { initiatorType: "script",
// nextHopProtocol: "h2",
// workerStart: 526.8099999520928,
// redirectStart: 0,
// ....
// decodedBodySize: 0,
// serverTiming: [],
// name: "https://d33wubrfki0l68.cloudfront.net/bundles/e2203d1b1c14952473222bcff4c58a8bd9fef14a.js",
// entryType: "resource",
// startTime: 315.5049999477342,
// duration: 231.48499999661
//}
// We can see this is a resource entry for a script loaded from cloudfront
Функция getEntriesByType() похожа на getEntries(), но она дает вам возможность фильтровать результаты.
Она позволяет запрашивать 6 типов:
- frame: экспериментальная функция, позволяющая разработчикам получать данные о том, сколько работы проделал браузер за один цикл обработки событий. Если браузер выполняет слишком много работы в одном цикле, частота падает, а пользовательский опыт будет плохим.
- resource: относится ко всем ресурсам, которые загружаются сайтом.
- mark: пользовательские маркеры, которые можно использовать для расчета скорости вашего кода.
- measure: позволяет легко измерить разницу между двумя отметками.
- paint: пиксели, отображаемые на экране.
- longtask: длинные задачи — это любые задачи, выполнение которых занимает более 50 мс.
Мы рассмотрим некоторые из этих типов в следующих разделах руководства. Для начала давайте посмотрим на простой пример:
const paintEntries = performance.getEntriesByType('paint')
// paint Entries[0] equals {
// name: "first-paint",
// entryType: "paint",
// startTime: 342.160000000149,
// duration: 0,
// }
// paintEntries[1] equals {
// name: "first-contentful-paint",
// entryType: "paint",
// startTime: 342.160000000149,
// duration: 0,
// }
getEntriesByName(entryName) фильтрует все записи по имени.
const nativeLogoPerfEntry = performance.getEntriesByName('https://alligator.io/images/alligator-logo3.svg')[0];
// It will return performance information related to the logo's performance:
// {initiatorType: "img",
// nextHopProtocol: "",
// workerStart: 539.6649999311194,
// ........
// name: "https://alligator.io/images/alligator-logo3.svg",
// entryType: "resource",
// startTime: 539.5149999530986,
// duration: 94.24000000581145
//}
Примечание: Если вам нужна информация более высокого уровня о производительности сайта, вы также можете вызвать performance.toJSON().
Аудит функций
Самым основным инструментом аудита определенных функций JavaScript является performance.now(), о котором мы говорили выше.
Вот пример его использования:
const firstNow = performance.now()
// This loop is just to simulate slow calculations
for (let i = 0; i < 100000; i++){
var ii = Math.sqrt(i)
}
const secondNow = performance.now()
const howLongDidOurLoopTake = secondNow - firstNow
// on my laptop it returns howLongDidOurLoopTake == 4.08500000089407 in milliseconds
Проблема с now в том, что с ним немного сложно работать, если у вас много показателей. Более полезным инструментом является функция mark, которая создает записи о производительности, которые вы можете запросить позже. Кроме того, вы можете комбинировать маркеры и создавать новые записи, используя measure.
performance.mark('beginSquareRootLoop');
// This loop is just to simulate slow calculations
for (let i = 0; i < 1000000; i++){
var ii = Math.sqrt(i);
}
performance.mark('endSquareRootLoop');
// Then anywhere in your code you can use
// We create a new entry called measureSquareRootLoop which combines our two marks
performance.measure('measureSquareRootLoop','beginSquareRootLoop', 'endSquareRootLoop');
console.log(performance.getEntriesByName('beginSquareRootLoop'));
// {detail: null,
// name: "beginSquareRootLoop",
// entryType: "mark",
// startTime: 3745.360000000801,
// duration: 0}
console.log(performance.getEntriesByName('measureSquareRootLoop'));
// {detail: null,
// name: "measureSquareRootLoop",
// entryType: "measure",
// startTime: 3745.360000000801, This is the same as beginSquareRootLoop
// duration: 9.904999984428287 shows the time it took to get from beginSquareRootLoop to endSquareRootLoop
//}
Навигационные данные
Навигация используется для определения важнейших шагов по созданию веб-страницы. Самый безопасный способ получить доступ к навигационным данным:
const navigationEntry = performance.getEntriesByType('navigation')[0]
Мы получили такой результат:
{
unloadEventStart: 213.41000002576038,
unloadEventEnd: 213.41000002576038,
domInteractive: 975.8100000326522,
domContentLoadedEventStart: 982.2649999987334,
domContentLoadedEventEnd: 1217.9650000180118,
domComplete: 2000.960000033956,
loadEventStart: 2001.044999982696,
loadEventEnd: 2008.6500000325032,
type: "reload",
redirectCount: 0,
initiatorType: "navigation",
nextHopProtocol: "",
workerStart: 2.5550000136718154,
redirectStart: 0,
redirectEnd: 0,
fetchStart: 2.5599999935366213,
domainLookupStart: 2.5599999935366213,
domainLookupEnd: 2.5599999935366213,
connectStart: 2.5599999935366213,
connectEnd: 2.5599999935366213,
secureConnectionStart: 0,
requestStart: 2.5599999935366213,
responseStart: 107.46500000823289,
responseEnd: 214.3950000172481,
transferSize: 0,
encodedBodySize: 0,
decodedBodySize: 0,
serverTiming: [],
name: "https://alligator.io/",
entryType: "navigation",
startTime: 0,
duration: 2008.6500000325032
}
Ресурсы
Каждый раз, когда страница загружает ресурс, мы можем найти его след в записях о производительности. Все, что нам нужно сделать, чтобы получить их, это запустить performance.getEntriesByType(‘resource’). К этим ресурсам относятся изображения, сценарии, файлы CSS и многое другое. Так, например, если мы хотим сосредоточиться на производительности изображений на сайте, мы можем запустить:
performance.getEntriesByType('resource').filter(resource=> resource.initiatorType == 'img')
Вот один из ресурсов, найденных на Alligator.io:
{
initiatorType: "img",
nextHopProtocol: "h2",
workerStart: 551.2149999849498,
redirectStart: 0,
redirectEnd: 0,
fetchStart: 551.3149999896996,
domainLookupStart: 0,
domainLookupEnd: 0,
connectStart: 0,
connectEnd: 0,
secureConnectionStart: 0,
requestStart: 0,
responseStart: 0,
responseEnd: 560.1850000093691,
transferSize: 0,
encodedBodySize: 0,
decodedBodySize: 0,
serverTiming: [],
name: "https://d33wubrfki0l68.cloudfront.net/39d2d2905588dad289b228deb021d51449f6143d/a3baf/images/logos/gatsby-logo.svg",
entryType: "resource",
startTime: 222.0450000022538,
duration: 338.1400000071153
}
Как вы можете видеть, эта запись имеет много значений 0 из-за ограничений CORS (это большой предел API синхронизации ресурсов). Таким образом, следующие свойства всегда будут возвращать 0: redirectStart, redirectEnd, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart и responseStart.
paint API
Paint API относится к событиям, которые рисуют пиксели в окне. Как мы видели в предыдущем фрагменте, у нас есть доступ к First Time to Paint и First Contentful Paint. Если вы работали с инструментами оптимизации внешнего интерфейса, такими как Lighthouse, вы должны быть знакомы с этими терминами. First Time to Paint – это когда на экране пользователя появляется первый пиксель. First Contentful Paint, первая содержательная отрисовка — это когда элемент, определенный в DOM, впервые визуализируется. Чтобы оптимизировать первую содержательную отрисовку, вы можете уменьшить количество скриптов и таблиц стилей, блокирующих рендеринг, использовать кэширование HTTP, оптимизировать загрузку JavaScript и многое другое!
Это полезные показатели, но они довольно ограничены, если вы пытаетесь понять, что видят ваши пользователи. Чтобы иметь хорошее представление о восприятии производительности вашим пользователем, нам нужно объединить несколько показателей.
Заключение
Performance API огромный и быстро меняется. Если вы хотите по-настоящему глубоко изучить эту тему, вам следует посетить страницу рабочей группы, где вы можете найти самые свежие проекты и рекомендации.
