События в JavaScript: addEventListener на практике

Содержание

О проверке кода. Примеры не запускались в браузере — в отличие от материалов по Node.js с фактическим выводом. Говорим прямо, а не ставим бейдж «проверено».

Интерактивность страницы — это реакция на события: клики, ввод, отправку форм. В JavaScript события — целая система со своими правилами. Разберём то, что нужно на практике.

addEventListener вместо onclick

const btn = document.querySelector('.btn');

// ❌ старый способ — только ОДИН обработчик
btn.onclick = () => console.log('клик');
btn.onclick = () => console.log('другой');   // перезаписал первый!

// ✅ современный — сколько угодно обработчиков
btn.addEventListener('click', () => console.log('первый'));
btn.addEventListener('click', () => console.log('второй'));  // оба сработают

addEventListener — стандарт навешивания событий. В отличие от onclick, он позволяет несколько обработчиков на одно событие, поддерживает опции и корректное снятие. Свойство onclick хранит только один обработчик и перезаписывается — этого достаточно редко.

Частые типы событий: click, input (ввод в поле), submit (отправка формы), keydown (нажатие клавиши), mouseover, change, scroll.

Объект event

Обработчик автоматически получает объект события:

btn.addEventListener('click', (event) => {
  event.target;       // элемент, по которому кликнули
  event.type;         // 'click'
  event.currentTarget; // элемент, на котором висит обработчик
});

input.addEventListener('input', (event) => {
  event.target.value; // текущий текст в поле
});

document.addEventListener('keydown', (event) => {
  event.key;          // 'Enter', 'Escape', 'a'...
});

event содержит всю информацию о событии. Самое частое — event.target (элемент-источник) и event.target.value (для полей ввода). Для клавиатуры — event.key, для мыши — event.clientX/clientY. Через этот объект и управляют поведением.

preventDefault: отменить действие браузера

// не дать форме перезагрузить страницу
form.addEventListener('submit', (event) => {
  event.preventDefault();    // отменяем стандартную отправку
  // ...обрабатываем данные через JavaScript
});

// не переходить по ссылке
link.addEventListener('click', (event) => {
  event.preventDefault();
});

event.preventDefault() отменяет стандартную реакцию браузера. Отправка формы перезагружает страницу, клик по ссылке ведёт на URL, клавиши прокручивают — всё это можно отменить, чтобы обработать самому. Это первое, что делают в обработчике submit при работе с формой через JavaScript.

Всплытие событий

document.querySelector('#parent').addEventListener('click', () => {
  console.log('сработал родитель');
});
document.querySelector('#child').addEventListener('click', () => {
  console.log('сработал ребёнок');
});
// клик по child выведет: 'сработал ребёнок', потом 'сработал родитель'
Всплытие: событие поднимается вверх
  1. 1 Клик по кнопкеСобытие рождается на самом элементе (target)
  2. 2 Всплывает к родителюСрабатывают обработчики на div-контейнере
  3. 3 Дальше вверхsection → body → document — по всей цепочке
  4. 4 stopPropagation()Останавливает всплытие, если нужно

Событие сначала срабатывает на самом элементе, затем поднимается («всплывает») вверх по родителям до document. Клик по кнопке внутри div вызовет обработчики и кнопки, и div, и всех родителей. Это не баг, а фундамент делегирования событий — мощного приёма. Остановить всплытие можно через event.stopPropagation(), но применяйте его осознанно — он ломает делегирование.

Снятие обработчика

// ❌ анонимную функцию снять НЕЛЬЗЯ — на неё нет ссылки
btn.addEventListener('click', () => console.log('клик'));
// removeEventListener не к чему привязать

// ✅ именованная функция — снимается корректно
function handleClick() { console.log('клик'); }
btn.addEventListener('click', handleClick);
btn.removeEventListener('click', handleClick);   // работает

// одноразовый обработчик — снимется сам
btn.addEventListener('click', handleClick, { once: true });

removeEventListener требует ту же функцию, что была при добавлении — значит, обработчик должен быть именованным, анонимную стрелку снять невозможно. Забытые обработчики — источник утечек памяти в долгоживущих приложениях. Для одноразовых событий удобна опция { once: true } — обработчик снимется автоматически после первого срабатывания.

Полезные опции

el.addEventListener('scroll', handler, { passive: true }); // не блокирует прокрутку
el.addEventListener('click', handler, { once: true });     // один раз
el.addEventListener('click', handler, { capture: true });  // на фазе погружения

{ passive: true } для scroll и touch говорит браузеру, что обработчик не вызовет preventDefault — это ускоряет прокрутку. Опции — то, чего лишён старый onclick, ещё одна причина использовать addEventListener.


Смежные темы: делегирование событий — один обработчик на многих; поиск элементов; валидация форм — preventDefault на submit. Полный список — в уроках JavaScript.

Частые вопросы

Почему addEventListener лучше onclick?
addEventListener позволяет повесить несколько обработчиков на одно событие, тогда как onclick хранит только один и перезаписывается. Плюс addEventListener даёт опции (once, passive) и позволяет корректно снять обработчик через removeEventListener.
Что такое объект event?
Аргумент, который автоматически получает функция-обработчик. В нём информация о событии: event.target — элемент-источник, event.type — тип, координаты мыши, нажатая клавиша. Через него управляют поведением события.
Что делает preventDefault?
Отменяет стандартное действие браузера: отправку формы, переход по ссылке, прокрутку. Например, в обработчике submit вызывают event.preventDefault(), чтобы обработать форму через JavaScript, а не перезагружать страницу.
Что такое всплытие событий?
Событие сначала срабатывает на самом элементе, потом поднимается вверх по родителям до document. Клик по кнопке внутри div вызовет обработчики и на кнопке, и на div. Это основа делегирования событий.
Как снять обработчик события?
removeEventListener с той же функцией, что и при добавлении. Поэтому обработчик должен быть именованной функцией, а не анонимной — анонимную снять нельзя, ведь на неё нет ссылки. Либо опция once для одноразового.