В JavaScript производительность веб-страницы во многом определяется эффективностью работы с событиями. Особенно это важно при измерении метрик Web Vitals, таких как Largest Contentful Paint (LCP), First Input Delay (FID) и Cumulative Layout Shift (CLS). Неправильная организация обработчиков событий может привести к задержкам отклика, блокировке основного потока и некорректным измерениям ключевых показателей пользовательского опыта.
Делегирование событий позволяет назначать один обработчик на родительский элемент, вместо того чтобы вешать обработчики на множество дочерних элементов. Это снижает нагрузку на DOM и сокращает количество операций добавления и удаления обработчиков.
Пример:
document.getElementById('list').addEventListener('click', function(event) {
const target = event.target;
if (target.tagName === 'LI') {
console.log('Нажат элемент списка:', target.textContent);
}
});
Вместо того чтобы навесить обработчик на каждый
<li>, достаточно одного обработчика на контейнере.
Такой подход уменьшает вероятность возникновения FID
проблем, так как основной поток не перегружается обработчиками.
События, которые срабатывают часто (scroll, resize, mousemove), могут создавать нагрузку на основной поток, что негативно отражается на LCP и FID. Для оптимизации используют throttle и debounce.
Throttle ограничивает выполнение функции через определённые интервалы времени:
function throttle(fn, wait) {
let last = 0;
return function(...args) {
const now = Date.now();
if (now - last >= wait) {
last = now;
fn.apply(this, args);
}
};
}
window.addEventListener('scroll', throttle(() => {
console.log('Прокрутка');
}, 200));
Debounce выполняет функцию только после прекращения серии событий:
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
window.addEventListener('resize', debounce(() => {
console.log('Изменение размера окна');
}, 300));
Для событий прокрутки и сенсорного ввода браузеры по умолчанию
блокируют основной поток, чтобы определить, будет ли вызван
preventDefault(). Если мы уверены, что блокировки не
требуется, стоит использовать passive слушатели:
window.addEventListener('scroll', handleScroll, { passive: true });
Это уменьшает задержку отклика, что положительно сказывается на FID.
Любые обработчики, которые остаются навешанными после того, как
элемент удалён из DOM, создают утечки памяти и замедляют обработку
событий. Обработчики следует удалять с помощью
removeEventListener:
const btn = document.getElementById('button');
function handleClick() {
console.log('Кнопка нажата');
}
btn.addEventListener('click', handleClick);
// Позже, когда обработчик больше не нужен
btn.removeEventListener('click', handleClick);
Внутри обработчиков событий не стоит выполнять тяжёлые синхронные
вычисления. Для оптимизации можно использовать Web
Workers или requestIdleCallback, чтобы выполнять
обработку асинхронно:
window.addEventListener('click', () => {
requestIdleCallback(() => {
heavyComputation();
});
});
Инструменты вроде Web Vitals JS позволяют фиксировать метрики и отслеживать, как задержки событий влияют на пользовательский опыт. Например, измерение FID с учётом оптимизированных обработчиков:
import { getFID } from 'web-vitals';
getFID((metric) => {
console.log(metric.name, metric.value);
});
Если задержки слишком высоки, это сигнализирует о необходимости оптимизации обработчиков: делегирование, throttle/debounce, passive listeners, перенос тяжёлой работы в фон.
scroll,
mousemove, resize — использовать
throttle или debounce и passive
listeners.click,
keydown, touchstart — минимизировать
синхронные операции, выполнять тяжёлую работу асинхронно.Эти меры позволяют снижать задержки, уменьшать нагрузку на основной поток и обеспечивать более корректное измерение метрик Web Vitals, что напрямую отражается на скорости отклика и стабильности интерфейса.