Browser API → Объекты документа и страницы → События
Основная идея
Обработка DOM-событий — это механизм, позволяющий выполнять JavaScript-код в ответ на действия пользователя или изменения состояния страницы. Существует три способа назначить обработчик: HTML-атрибут, DOM-свойство и addEventListener.
Ключевые аспекты
addEventListener — современный способ, позволяет несколько обработчиков на одно событие
removeEventListener — удаляет обработчик, требует ссылку на ту же функцию
DOM-свойство (onclick) — только один обработчик на событие
HTML-атрибут — устаревший способ, смешивает HTML и JS
Объект события (event) — содержит информацию о произошедшем событии
Плюсы addEventListener
Множество обработчиков на одно событие
Возможность удаления конкретного обработчика
Поддержка фазы захвата (capture)
Опция once для однократного выполнения
Опция passive для оптимизации производительности
Минусы разных подходов
HTML-атрибуты нарушают разделение логики и представления
DOM-свойство позволяет только один обработчик
removeEventListener требует именованную функцию (анонимную не удалить)
Частые ошибки на собеседованиях
Пытаются удалить анонимную функцию через removeEventListener — это невозможно
Путают this в обработчике: в addEventListener это элемент, в стрелочной функции — внешний контекст
Забывают про третий аргумент addEventListener (options/useCapture)
Не знают про опцию once для одноразовых обработчиков
Введение и проблематика
Чтобы сделать веб-страницу интерактивной, нужно "научить" JavaScript реагировать на действия пользователя. Для этого существует механизм обработчиков событий (event handlers) — функций, которые вызываются при наступлении определённого события.
Обработчик события — это функция, которая выполняется в ответ на определённое событие (клик, ввод текста, загрузка страницы и т.д.).
Зачем нужно уметь управлять обработчиками?
Добавление интерактивности — реакция на клики, ввод, скролл
Оптимизация — удаление ненужных обработчиков для экономии памяти
Динамические интерфейсы — добавление/удаление обработчиков в зависимости от состояния
Предотвращение утечек памяти — очистка обработчиков при удалении элементов
Базовая теория
Три способа назначения обработчиков
Существует три способа назначить обработчик события:
graphLR A[Способы назначения]--> B[HTML-атрибут] A --> C[DOM-свойство] A --> D[addEventListener] B --> B1["onclick='...'"] C --> C1["element.onclick = fn"] D --> D1["element.addEventListener()"]
Способ 1: HTML-атрибут (устаревший)
html
<!-- Прямо в HTML --><buttononclick="alert('Клик!')">Нажми меня</button><!-- Вызов функции --><buttononclick="handleClick()">Нажми меня</button><!-- С передачей event --><buttononclick="handleClick(event)">Нажми меня</button>
js
functionhandleClick(event) {console.log('Клик по кнопке');console.log(event.target); // кнопка}
🚫
Не рекомендуется! Этот способ смешивает HTML и JavaScript, усложняет поддержку и нарушает принцип разделения ответственности.
Проблемы HTML-атрибутов
Код в HTML — плохая практика
Можно написать только простые выражения
Сложно отлаживать
Невозможно добавить несколько обработчиков
Способ 2: DOM-свойство
js
constbutton=document.querySelector('button');// Назначение обработчикаbutton.onclick=function(event) {console.log('Клик!');console.log(this); // button — сам элемент};// Или через именованную функциюfunctionhandleClick(event) {console.log('Клик!');}button.onclick = handleClick;
Удаление обработчика через DOM-свойство
js
// Удаление — просто присвоить nullbutton.onclick =null;
Ограничение: только один обработчик
js
constbutton=document.querySelector('button');button.onclick=function() {console.log('Первый обработчик');};// Второй обработчик ПЕРЕЗАПИШЕТ первый!button.onclick=function() {console.log('Второй обработчик');};// При клике выведется только "Второй обработчик"
⚠️
DOM-свойство позволяет назначить только ОДИН обработчик на каждый тип события. Повторное присваивание перезаписывает предыдущий обработчик.
constbutton=document.querySelector('button');// Можно добавить несколько обработчиков на одно событие!button.addEventListener('click',function() {console.log('Первый обработчик');});button.addEventListener('click',function() {console.log('Второй обработчик');});button.addEventListener('click',function() {console.log('Третий обработчик');});// При клике выполнятся ВСЕ три в порядке добавления
Опции addEventListener
js
element.addEventListener('click', handler, { capture:false,// фаза захвата (false = всплытие) once:true,// обработчик сработает один раз и удалится passive:true,// обработчик не будет вызывать preventDefault() signal:controller.signal // для отмены через AbortController});
js
// once — обработчик автоматически удалится после первого вызоваconstbutton=document.querySelector('button');button.addEventListener('click', () => {console.log('Этот обработчик сработает только один раз!');}, { once:true });// Клик → "Этот обработчик сработает только один раз!"// Повторный клик → ничего (обработчик уже удалён)
Для удаления обработчика нужна ссылка на ту же самую функцию, которая была передана в addEventListener. Анонимную функцию удалить невозможно!
js
constbutton=document.querySelector('button');// Именованная функцияfunctionhandleClick() {console.log('Клик!');}// Добавлениеbutton.addEventListener('click', handleClick);// Удаление — та же самая функцияbutton.removeEventListener('click', handleClick);
constbutton=document.querySelector('button');functionhandleClick() {console.log('Клик! Обработчик удаляется...');// Удаляем сам себяbutton.removeEventListener('click', handleClick);}button.addEventListener('click', handleClick);
Удаление с учётом options
js
constbutton=document.querySelector('button');functionhandleClick() {console.log('Capture phase click');}// Добавление в фазе захватаbutton.addEventListener('click', handleClick, { capture:true });// Удаление — нужно указать те же опции!button.removeEventListener('click', handleClick, { capture:true });// Или сокращённо:button.removeEventListener('click', handleClick,true);
Контекст this в обработчиках
В обычной функции
js
constbutton=document.querySelector('button');button.addEventListener('click',function() {// this = элемент, на котором висит обработчикconsole.log(this); // <button>...</button>this.classList.add('clicked');});
В стрелочной функции
js
constbutton=document.querySelector('button');button.addEventListener('click', () => {// this = внешний контекст (обычно window или undefined в strict mode)console.log(this); // Window или undefined// Используйте event.target или event.currentTarget вместо this});
⚠️
Стрелочные функции не имеют собственного this. Если нужен доступ к элементу через this, используйте обычную функцию или event.currentTarget.
Разница между target и currentTarget
js
document.querySelector('.parent').addEventListener('click',function(e) {console.log(e.target); // элемент, на котором произошёл кликconsole.log(e.currentTarget); // элемент, на котором висит обработчикconsole.log(this); // то же, что currentTarget});
Объект события (Event)
js
element.addEventListener('click',function(event) {// Тип событияconsole.log(event.type); // "click"// Элементыconsole.log(event.target); // на чём кликнулиconsole.log(event.currentTarget); // где висит обработчик// Координаты (для событий мыши)console.log(event.clientX,event.clientY); // относительно viewportconsole.log(event.pageX,event.pageY); // относительно документа// Модификаторыconsole.log(event.ctrlKey); // зажат ли Ctrlconsole.log(event.shiftKey); // зажат ли Shiftconsole.log(event.altKey); // зажат ли Altconsole.log(event.metaKey); // зажат ли Cmd/Win// Методыevent.preventDefault(); // отменить действие по умолчаниюevent.stopPropagation(); // остановить всплытие});
Сравнение способов
Критерий
HTML-атрибут
DOM-свойство
addEventListener
Множественные обработчики
Нет
Нет
Да
Удаление обработчика
Сложно
Просто (= null)
removeEventListener
Фаза захвата
Нет
Нет
Да
Опция once
Нет
Нет
Да
Разделение HTML/JS
Нет
Да
Да
Рекомендуется
Нет
Для простых случаев
Да
Пограничные кейсы
Множественное добавление одного обработчика
js
constbutton=document.querySelector('button');functionhandleClick() {console.log('Клик!');}// Добавляем один и тот же обработчик дваждыbutton.addEventListener('click', handleClick);button.addEventListener('click', handleClick);// При клике выведется только ОДИН раз "Клик!"// addEventListener игнорирует дубликаты (одинаковая функция + тип + options)
Удаление во время обработки
js
constbuttons=document.querySelectorAll('button');buttons.forEach(button => {button.addEventListener('click',functionhandler() {console.log('Кнопка кликнута');// Безопасно удалять обработчик внутри него самогоbutton.removeEventListener('click', handler); });});
Вопросы интервьюера
Q: Как удалить анонимный обработчик события?
Напрямую никак — нет ссылки на функцию. Варианты: использовать именованную функцию, опцию { once: true }, или AbortController с опцией { signal }.
Q: Что такое опция passive и зачем она нужна?
passive: true говорит браузеру, что обработчик не будет вызывать preventDefault(). Это позволяет браузеру не ждать завершения обработчика для scroll/touch событий, делая прокрутку более плавной.
Q: Чем отличается event.target от event.currentTarget?
target — элемент, на котором фактически произошло событие. currentTarget — элемент, на котором установлен обработчик. При всплытии они могут различаться.
Q: Почему в стрелочной функции this не указывает на элемент?
Стрелочные функции не имеют собственного this, они берут его из внешнего лексического окружения. Используйте event.currentTarget или обычную функцию.