Оптимизация обработчиков событий
Оптимизация обработчиков событий
{ once: true } — одноразовый обработчик
// Сработает один раз и автоматически удалится
btn.addEventListener('click', handleClick, { once: true })
// Эквивалент вручную:
function handleOnce(e) {
handleClick(e)
btn.removeEventListener('click', handleOnce)
}
btn.addEventListener('click', handleOnce)
Passive listeners — улучшение производительности
// НЕОПТИМАЛЬНО: браузер ждёт до вызова обработчика
// (вдруг там будет preventDefault?)
document.addEventListener('touchstart', handler)
document.addEventListener('scroll', handler)
// ОПТИМАЛЬНО: говорим браузеру, что preventDefault не будет
document.addEventListener('touchstart', handler, { passive: true })
document.addEventListener('scroll', handler, { passive: true })
// Браузер может сразу начать прокрутку, не ожидая JS
Passive listeners особенно важны для
touchstart, touchmove, scroll, wheel. Они устраняют задержку прокрутки — браузер не ждёт завершения обработчика перед скроллом.AbortController — управление несколькими обработчиками
// Создаём контроллер
const controller = new AbortController()
const { signal } = controller
// Добавляем обработчики с signal
document.addEventListener('keydown', handleKey, { signal })
window.addEventListener('resize', handleResize, { signal })
document.addEventListener('click', handleClick, { signal })
// Удаляем ВСЕ одним вызовом!
controller.abort()
// Все три обработчика удалены
// Практично в компонентах:
class Modal {
constructor() {
this.controller = new AbortController()
this.setupEvents()
}
setupEvents() {
const { signal } = this.controller
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') this.close()
}, { signal })
document.addEventListener('click', (e) => {
if (e.target === this.overlay) this.close()
}, { signal })
}
destroy() {
this.controller.abort() // убираем всё одной строкой
}
}
Снятие обработчиков при удалении элемента
// Паттерн для компонентов: хранить ссылки на обработчики
class Dropdown {
constructor(element) {
this.element = element
this.controller = new AbortController()
const { signal } = this.controller
element.addEventListener('click', this.toggle.bind(this), { signal })
document.addEventListener('click', this.handleOutsideClick.bind(this), { signal })
}
toggle() { /* ... */ }
handleOutsideClick(e) { /* ... */ }
destroy() {
this.controller.abort()
this.element.remove()
}
}
Делегирование лучше если:
- Много однотипных элементов (список, таблица)
- Элементы добавляются/удаляются динамически
- Нужно единообразно обрабатывать целую группу
Отдельные обработчики лучше если:
- Элемент один, логика сложная
- Нужна точная очистка при удалении (AbortController)
- Элемент статичный и изолированный
// Гибридный подход: делегирование + AbortController
function setupList(listEl) {
const ctrl = new AbortController()
listEl.addEventListener('click', handleListClick, {
signal: ctrl.signal
})
listEl.addEventListener('keydown', handleListKeydown, {
signal: ctrl.signal
})
return () => ctrl.abort() // функция очистки
}
const cleanup = setupList(document.querySelector('.list'))
// Позже:
cleanup() // удаляет все обработчики