JavaScript addEventListener() — метод, который нужен любому фронтенд-разработчику

Гайд по обработке событий в JS

JavaScript addEventListener() — метод, который нужен любому фронтенд-разработчику

addEventListener() — это метод, который позволяет элементу на странице «услышать» пользователя. Клик, нажатие клавиши, скролл — всё это события, и с помощью addEventListener() мы можем на них реагировать: запускать функцию, менять интерфейс, отправлять данные.

В этой статье разберёмся, как работает этот метод, что он принимает и как правильно его использовать.

Введение в addEventListener()

Метод addEventListener() — это базовый способ отслеживать действия пользователя на странице. Он позволяет «подписаться» на определённое событие — клик, прокрутку, нажатие клавиши — и выполнить нужную функцию, когда это событие происходит.

Это основной инструмент для работы с интерактивностью в браузере. Без него не нажмётся кнопка, не откроется модальное окно и не отправится форма. И хоть синтаксис простой, у метода есть нюансы: параметры, особенности работы с разными фазами событий, удаление обработчиков, оптимизация с passive и другие полезные штуки.

Синтаксис

Метод addEventListener() вызывается у любого DOM-элемента (включая глобальные объекты window и document) и выглядит так:

element.addEventListener(type, listener); element.addEventListener(type, listener, options); element.addEventListener(type, listener, useCapture);

Минимум нужно два аргумента — тип события и функция. Но можно (а иногда нужно) добавить третий — с настройками:

  • event — строка с названием события (click, input, keydown, scroll).
  • listener — функция, которая должна выполниться, когда произойдёт событие. Её ещё называют обработчиком (handler).
  • options (необязательный параметр) — объект с дополнительными настройками: capture, once, passive и так далее.
  • useCapture (необязательный параметр) — логический флаг. Если передать true, обработчик будет срабатывать на этапе погружения, а не на всплытии (об этом расскажем чуть позже). По умолчанию false

Например, у нас есть кнопка, и мы хотим повесить на неё реакцию по клику. Мы можем сделать это с анонимной функцией:

// Находим элемент кнопки на странице
const button = document.querySelector('button');
// Добавляем обработчик события 'click'
button.addEventListener('click', () => {
  alert('Кнопка нажата!');
});

А вот вариант с именованной функцией — его проще читать и переиспользовать:

// Функция-обработчик клика
function handleClick() {
  console.log('Клик!');
}
// Привязываем обработчик к кнопке
button.addEventListener('click', handleClick);

Оба способа работают одинаково: обработчик привяжется и отработает. Но! Если вы используете анонимную функцию, её потом нельзя снять через removeEventListener(), поскольку на неё не будет ссылки.

Поэтому принцип такой:

  • Если логика простая и обработчик не нужно потом удалять — можно использовать анонимную функцию.
  • Если планируете снимать обработчик или переиспользовать его — лучше использовать отдельную именованную функцию.

Параметры

Разберём подробно все параметры.

type

Тип события, за которым будем следить. Обязательное поле, пишется как строка:

'click' | 'keydown' | 'input' | 'scroll' | 'submit' |

👉 Регистр важен: 'click' — работает, 'CLICK' — нет.

listener

Функция или объект, который будет вызван при наступлении события. Обычно это просто функция:

element.addEventListener('click', (event) => {
  console.log(event.target);
});

Можно передать объект с методом handleEvent(), но это редкость. В 99% случаев — обычная функция.

options

Здесь можно тонко настроить, как именно будет работать обработчик. Используются такие поля:

  • capture — слушать событие на стадии погружения, а не всплытия. По умолчанию false.
  • once — обработчик сработает один раз, потом автоматически удалится.
  • passive — вы обещаете, что внутри не будет event.preventDefault(). Это даёт браузеру возможность заранее не блокировать действия.
  • signal — можно указать AbortSignal, чтобы удалить обработчик через abort().

Удобно, когда нужно отменить группу событий сразу.

Параметр passive помогает оптимизировать интерфейс. Если вы вешаете обработчик на прокрутку (wheel, touchstart, touchmove), браузер по умолчанию тормозит выполнение. Он ждёт, не вызовете ли вы event.preventDefault() — метод, который отменяет стандартное поведение (ту же самую прокрутку).И пока он не поймёт, вызывается preventDefault() или нет, прокрутка задерживается, интерфейс ощущается менее отзывчивым. Но если явно указать { passive: true }, браузер точно будет знать, что отмены не будет, и не станет тормозить прокрутку:

window.addEventListener('wheel', () => {
  console.log('Прокручиваем!');
}, { passive: true });

При этом passive: true не нужен для click, input, change — там это ни на что не влияет. Событие scroll нельзя отменить вообще, поэтому passive с ним бессмысленен. Некоторые браузеры уже ставят passive: true по умолчанию для прокрутки, но лучше указать явно, чтобы поведение было везде предсказуемым.

useCapture

Устаревший параметр, но ещё работает. Если вместо объекта передать просто true или false, это будет то же самое, что и прописать options.capture. Но сейчас рекомендуют использовать объект с параметрами.

element.addEventListener('click', handler, true); // эквивалент capture: true

Возвращаемое значение

Метод addEventListener() ничего не возвращает. Его задача — просто добавить обработчик события и всё:

const button = document.querySelector('.alm-load-more-btn');
const result = button.addEventListener('click', () => {
  console.log('Кнопка нажата!');
});
console.log(result); // undefined

Метод возвращает undefined, потому что просто навешивает обработчик и не предполагает, что нам нужно что-то сохранять. Всё дальнейшее управление — через саму функцию и removeEventListener(). Об этом — в следующем разделе.

Примеры использования

Метод addEventListener() — это основа для работы с интерактивностью на странице. Важно, что вы можете навесить сколько угодно обработчиков на один и тот же элемент — и каждый будет работать независимо. Дальше на примерах разберём самые популярные сценарии: от кликов до передачи параметров, от window до удаления обработчиков. 

Обработка кликов мышью

Самый частый сценарий: пользователь кликает на кнопку — и что-то происходит. Пример:

// Находим кнопку на странице
const button = document.querySelector('.buy-button');
// Вешаем обработчик на клик
button.addEventListener('click', () => {
  console.log('Товар добавлен в корзину!');
});

Если вы хотите заблокировать повторные клики (например, от двойного нажатия или спама), можно временно отключить кнопку внутри обработчика:

button.addEventListener('click', () => {
  button.disabled = true; // блокируем кнопку
  console.log('Заказ оформляется...');
});

А если нужно, чтобы обработчик сработал только один раз, используем once:

button.addEventListener('click', () => {
  console.log('Куплено!');
// Обработчик выполнится только один раз
}, { once: true });

Кстати, once: true удобно использовать в аналитике или A/B-тестах, когда нужно засчитать только первое взаимодействие — например, один клик по кнопке «Попробовать». Это избавляет от дублирующихся событий и делает данные чище, без лишнего шума.

Обработка наведения курсора мыши

Иногда одного псевдокласса :hover в CSS недостаточно. Например, мы хотим показать подсказку, загрузить данные при наведении или анимировать элемент. В таких случаях используют addEventListener() на событиях курсора —mouseenter и mouseleave.

Работает это так:

// Находим элемент
const card = document.querySelector('.product-card');
// Наводим курсор — срабатывает событие
card.addEventListener('mouseenter', () => {
// Добавляем класс подсветки 
  card.classList.add('highlight');
});
// Уводим курсор
card.addEventListener('mouseleave', () => {
// Убираем подсветку
  card.classList.remove('highlight');
});

В инструментах разработчика можно увидеть, как динамически меняются стили:

Наведение курсора — частый триггер для интерфейсов: подсказки, анимации, всплывающие блоки. Чтобы отслеживать это, используют события mouseenter и mouseleave. В отличие от mouseover и mouseout, они не реагируют на перемещения внутри вложенных элементов — срабатывают только при входе и выходе с самого блока. Поэтому нет лишних срабатываний и дёрганий интерфейса.

Обработка ввода текста

Обработка текста на лету — это база для валидации форм, автосохранения, подсказок, поиска и других интерактивных штук. Для этого используют событие input — оно срабатывает каждый раз, когда пользователь вводит или изменяет текстовое поле.

const input = document.querySelector('#username');
// Обновляем заголовок по мере ввода
input.addEventListener('input', (event) => {
  const value = event.target.value;
  document.querySelector('h1').textContent = `Привет, ${value || 'незнакомец'}!`;
});

Событие input удобнее, чем change, потому что реагирует сразу, без потери фокуса. А keydown/keyup работают с клавишами, но не всегда учитывают автозаполнение, вставку текста и другие действия.

А ещё, чтобы не перегружать интерфейс и не слать запросы на каждый ввод символа, можно использовать debounce — небольшую задержку между вводом и вызовом обработчика. Это особенно полезно для живого поиска, автосохранения или отправки данных на сервер: функция сработает только после того, как пользователь перестанет печатать.

Использование нескольких обработчиков событий на одном элементе

Иногда одного обработчика недостаточно. Например, по клику на кнопку мы хотим одновременно изменить её внешний вид и отправить данные куда-то в аналитику. С addEventListener можно навесить несколько обработчиков одного и того же события — они будут срабатывать независимо друг от друга, в порядке добавления.

const button = document.querySelector('button');
// Первый обработчик — визуальный эффект
button.addEventListener('click', () => {
  button.classList.add('clicked');
});
// Второй обработчик — отправка события в аналитику
button.addEventListener('click', () => {
  console.log('Клик засчитан в аналитике');
});

Одно событие click — сколько угодно действий. В нашем случае два обработчика вызываются по очереди.

Если нужно остановить дальнейшее выполнение, можно использовать event.stopImmediatePropagation() — но только если вы точно уверены, что другие обработчики не нужны.

Передача параметров функции при возникновении события

Иногда обработчику нужно знать не только что произошло, но и с каким элементом это связано. Например, у нас есть несколько кнопок «Купить» на странице интернет-магазина, и мы хотим узнать, какой именно товар купили. Обычный обработчик передаёт только event, но есть способы прокинуть и свои данные.

Первый способ — использовать функцию-обёртку:
const buyButton = document.querySelector('#buy-shirt');
function handleBuy(productId) {
  console.log(`Куплен товар с ID: ${productId}`);
}
// Передаём ID вручную через замыкание
buyButton.addEventListener('click', () => {
  handleBuy(42);
});

Здесь внутри анонимной функции мы передаём ID, а event можно вообще не использовать, если он не нужен.Второй способ — использовать метод bind():

function handlePurchase(productId, event) {
 log.textContent = `Покупка товара с ID: ${productId}`;
 console.log("Тип события:", event.type);
}
// Передаём ID 42 через bind
button.addEventListener("click", handlePurchase.bind(null, 42));

Метод bind(null, 42) создаёт новую функцию, где productId = 42, а event передаётся как обычно. И поскольку bind() создаёт новую функцию, её нельзя потом снять через removeEventListener, если не сохранили ссылку. Это может быть критично в динамических интерфейсах.

Эти трюки удобно использовать в списках товаров, когда есть разные ID, когда мы отслеживаем клики по разным зонам страницы, когда переиспользуем компоненты и используем события внутри циклов. Получается не просто клик, а клик с контекстом. 

Добавление обработчика событий к объекту window

События можно отслеживать не только на конкретных кнопках или элементах интерфейса, но и на всём окне браузера — объекте window. Это удобно, когда нужно реагировать на глобальные действия пользователя.

Например, отследить изменение размера окна:

window.addEventListener('resize', () => {
  console.log('Окно изменило размер:', window.innerWidth, 'px');
});

Это полезно, если нужно адаптировать поведение интерфейса на лету — показывать или скрывать элементы, менять классы, перестраивать компоненты в зависимости от ширины окна. Такое поведение нельзя реализовать только через CSS, потому что медиазапросы управляют стилями, а не логикой. А если нужно подгрузить другой компонент, отключить фичу или изменить структуру DOM — поможет обработчик resize. Допустим, при ширине меньше 1000px мы хотим включить мобильное меню:

function toggleMobileMenu() {
  // Находим элемент меню по классу
  const menu = document.querySelector('.mobile-menu');
  // Если ширина окна меньше 1 000 пикселей — включаем мобильное меню
  if (window.innerWidth < 1000) {
    // добавляем класс, который показывает меню
    menu.classList.add('active'); 
  } else {
    // убираем класс, если ширина больше
    menu.classList.remove('active'); 
  }
}
// Подписываемся на событие изменения размера окна
window.addEventListener('resize', toggleMobileMenu);
// вызываем функцию сразу, чтобы состояние меню соответствовало текущей ширине
toggleMobileMenu();

Использование removeEventListener()

Рано или поздно обработчик событий нужно снять. Пользователь закрыл модалку — и клик по кнопке больше не нужен. Или компонент в одностраничном приложении (SPA) размонтировался, и слушатель уже не должен висеть. Для этого используют метод removeEventListener().

Но есть нюанс: чтобы снять обработчик, нужно передать точно ту же функцию и те же параметры, что использовались при установке. Иначе ничего не произойдёт.

element.addEventListener("click", handleClick, true);
element.removeEventListener("click", handleClick, false); // ❌ Не сработает, разные параметры
element.removeEventListener("click", handleClick, true); // ✅ Сработает

Если не снять слушатель, он продолжит висеть в памяти — даже если элемент удалён из DOM. Это может привести к утечкам и лагам, особенно в сложных интерфейсах, где обработчиков много. В SPA-фреймворках (React, Vue и так далее) это критично: каждый новый рендер может создать новый обработчик, а старый так и останется активным.

Чтобы без проблем снять обработчик, используют именованные функции или сохраняют обработчики в переменные:

// Создаём обработчик и сохраняем в переменную
const handleClick = () => {
  console.log('Клик по кнопке!');
};
// Назначаем обработчик
button.addEventListener('click', handleClick);
// Позже легко снимаем
button.removeEventListener('click', handleClick);

Если бы вместо функции handleClick была анонимная функция () => {}, снять её потом было бы невозможно — потому что ссылка бы потерялась.

Важно помнить:

  • Анонимные функции нельзя удалить. Потому что вы не сможете передать в removeEventListener() ту же самую функцию.
  • Если повесили одну и ту же функцию дважды, но одну с capture: false , а другую с capture: true — придётся удалять каждую по отдельности.

А есть более радикальный способ, чтобы не заморачиваться с хранением всех функций и параметров, — AbortController. Он позволяет связать сразу несколько обработчиков с одним сигналом — и потом одной командой снять их все. Вот как это работает:

// Создаём новый AbortController
const controller = new AbortController();
// Вешаем обработчик события 'click' на элемент
// Передаём в параметры объект { signal: controller.signal }
// Это связывает обработчик с контроллером
element.addEventListener('click', () => {
  console.log('Клик!');
}, { signal: controller.signal });
// Позже, когда нужно убрать обработчик
controller.abort(); // Сразу снимает обработчик, связанный с этим сигналом

Очень удобно в динамических интерфейсах — например, когда компоненты в SPA размонтируются и надо подчистить всё сразу без ручных removeEventListener().

Понимание всплытия и погружения событий

Когда пользователь кликает на элемент, браузер не просто вызывает обработчик — событие проходит через всё DOM-дерево два раза:

  1. Фаза погружения (Capture) — событие идёт сверху вниз по DOM-дереву, от windowdocumenthtmlbody и так далее, до самого целевого элемента.
  2. Цель — событие достигает того самого элемента, по которому кликнули.
  3. Фаза всплытия (Bubbling) — событие поднимается обратно вверх по дереву DOM, также позволяя родительским элементам отреагировать.

Такой механизм позволяет делать много полезных штук.

Например, обработку кликов на контейнере-обёртке. Допустим, у нас есть карточка товара с кучей вложенных элементов: картинка, название, кнопки. Чтобы не навешивать обработчики на каждый из них, можно повесить один обработчик на родителя, а внутри смотреть, по какому элементу кликнули. Это называется делегированием событий. Так мы сокращаем количество обработчиков, упрощаем код и избавляемся от проблем с динамически созданными элементами — новые кнопки тоже будут отлавливаться обработчиком, даже если появились позже.

А ещё благодаря всплытию и погружению можно отменять действия до того, как они дошли до цели — например, перехватить клик на родителе, чтобы он не сработал на кнопке. Или наоборот — пропустить событие наверх, чтобы родитель принял решение, как реагировать. Бывает полезно, если нужно запретить действия на вложенных элементах.

По умолчанию addEventListener() срабатывает на этапе всплытия. Но иногда может понадобиться поймать событие раньше — и тут пригодится useCapture. Дальше на примерах разберём, как всем этим управлять.

Использование параметра useCapture

Если хотите перехватить событие на этапе погружения (пока оно ещё идёт к цели), нужно использовать параметр capture:

element.addEventListener('click', handler, { capture: true });

Такой обработчик сработает на этапе погружения, а не на всплытии. Это удобно, если нужно отловить событие раньше других обработчиков. Так мы контролируем поведение вложенных компонентов и можем реализовать делегирование не вглубь, а наоборот — «сверху вниз».

Но обычно этого не требуется — всплытия хватает за глаза. Поэтому по умолчанию стоит capture: false.

Изначально третьим параметром в addEventListener() был просто булевый флаг — useCapture. Сейчас стандартный способ — передавать объект { capture: true }. Это современнее, удобнее и позволяет сразу указать дополнительные опции { once: true } или { passive: true }.

Частые ошибки и советы

Ошибки при работе с addEventListener случаются и у начинающих, и у опытных разработчиков. Посмотрим, как их обойти.

Элемент не найден

Пытаетесь добавить обработчик на элемент, который ещё не появился в DOM. Такое часто случается, если скрипт выполняется до того, как страница полностью загрузилась, например если код стоит в <head> без defer или если элемент создаётся динамически.

Решается просто: вешаем обработчики внутри DOMContentLoaded или в конце <body> либо проверяем существование элемента:

// Ждём, пока загрузится весь DOM
document.addEventListener('DOMContentLoaded', () => {
  // Ищем кнопку по ID
  const button = document.querySelector('#myButton');
    // Проверяем, что кнопка действительно найдена
  if (button) {
    // Вешаем обработчик клика
    button.addEventListener('click', () => {
      console.log('Клик!');
    });
  }
});

Делегирование и всплытие

Иногда событие не срабатывает там, где нужно, потому что перехватывается родителем или не доходит до нужного элемента. Чтобы исправить это и при этом не вешать обработчик на каждый вложенный элемент, используют делегирование: вешают слушатель на общий контейнер и внутри проверяют, куда именно пришёл клик. 

Выглядит это так:

// Находим общий родительский элемент списка
const list = document.querySelector('ul');
// Вешаем обработчик кликов на весь список
list.addEventListener('click', (event) => {
  // Проверяем, был ли клик на пункте списка (LI)
  if (event.target.tagName === 'LI') {
    // Выводим текст пункта, по которому кликнули
    console.log('Клик по пункту:', event.target.textContent);
  }
});

Такой подход позволяет правильно отлавливать события даже на динамически добавленных элементах и избавляет от лишних обработчиков.

Забыли снять обработчик

Одна из самых коварных ошибок. Когда мы вешаем обработчик через addEventListener(), он остаётся висеть в памяти, пока мы явно не удалим его с помощью removeEventListener(). Но как уже было сказано, если используем анонимную функцию (например, button.addEventListener('click', () => { ... })), то потом уже нельзя её удалить, потому что на неё нет ссылки.

Если элемент удалится из DOM, а обработчик останется, браузер не сможет его нормально очистить. Возникнет утечка памяти: приложение будет сжирать всё больше ресурсов, а интерфейс начнёт тормозить.

Поэтому повторим:

  • Используем именованные функции или сохраняем обработчики в переменные, чтобы потом их можно было передать в removeEventListener.
  • Если обработчиков много, используем AbortController — он позволяет массово отключить все связанные обработчики.

Опечатка

Написали addEventLisener вместо addEventListener или cilck вместо click — и всё, час отладки обеспечен. Такую ошибку легко пропустить, особенно если не используете автодополнение или работаете в спешке. Настройте ESLint — он отлавливает такие опечатки ещё до запуска.

Неправильный тип события

Хотели обработать клик — а повесили mousedown или hover (а его вообще не существует!). Убедитесь, что такое событие реально есть и подходит для задачи. Полный список событий — мышь, клавиатура, тач, фокус, формы, ресурсы, Web API и так далее — можно посмотреть на MDN.

DevTools в браузере

При работе с событиями полезно использовать инструменты разработчика. Вкладка Event Listeners (Прослушиватели событий) в инспекторе покажет, какие события уже навешены, на что и где, и какие типы точно используются.

Чтобы проверить конкретный элемент, нажимаем на него правой кнопкой в DevTools, выбираем Inspect, потом во вкладке событий находим нужное — например, click. Там сразу видим, какая функция добавляет обработчик и в каком файле:

Поддерживаемые браузеры

Метод addEventListener() поддерживается во всех современных браузерах — Chrome, Firefox, Safari, Edge и даже в Internet Explorer начиная с версии 9.

Вам слово

Приходите к нам в соцсети поделиться своим мнением о методе и почитать, что пишут другие. А ещё там выходит дополнительный контент, которого нет на сайте: шпаргалки, опросы и разная дурка. В общем, вот тележка, вот ВК — велком!

Обложка:

Алексей Сухов

Корректор:

Елена Грицун

Соцсети:

Юлия Зубарева

Вёрстка:

Егор Степанов

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
А вы читали это?
hard
[anycomment]
Exit mobile version