Введение в события JS: DOM API

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

С точки зрения кода, все действия представлены событиями. События в браузере являются частью стандарта DOM. Вот некоторые из них:

  • click
  • submit
  • keyup/keydown
  • focus
  • contextmenu
  • mouseover
  • mousedown/mouseup

Как видите, их детализация достаточно высокая, набор текста раскладывается на два события: кнопка зажата и кнопка отпущена. Кроме этого, есть специальное событие keypress, которое позволяет отличать горячие клавиши от нормального ввода.

Самый простой способ попробовать события в действии, это использование специализированных атрибутов:

<button onclick="alert('Бум!')"></button>
<div onclick="alert(this.innerHTML)">Бум!</div>

Нажмите эту для демонстрации.

Обратите внимание на следующие моменты:

  1. JS при таком использовании задается, как строка текста. А значит, вы никаким образом не отследите возникающие внутри ошибки до тех пор, пока не инициируете событие.
  2. В этой строке должен быть вызов функции, то есть так onclick="alert" не заработает.
  3. Внутри подобных атрибутов доступен this, который ссылается на текущий элемент.

Обработчик события можно определить и снаружи:

<script>
  // Само событие передаётся в обработчик первым параметром. Об этом мы поговорим ниже.
  const getBoom = () => alert('Boom!');
</script>

<button onclick="getBoom()"></button>

Ну, и конечно, через свойство элемента в DOM:

<button id="myButton"></button>
const button = document.getElementById('myButton');
button.onclick = () => alert('Boom!');

В такой ситуации мы не вызываем обработчик, а только устанавливаем его в свойство onclick.

У этого способа есть один недостаток, который актуален там, где на странице есть множество скриптов, работающих независимо на одних и тех же элементах. Он заключается в том, что невозможно повесить одновременно несколько обработчиков. Если перезаписать свойство onclick, старый обработчик будет потерян безвозвратно.

На уровне атрибутов эта проблема не решаема, но в DOM есть метод, позволяющий повесить множество обработчиков на один элемент.

<button id="myButton"></button>
const button = document.getElementById('myButton');

button.addEventListener('click', () => alert('Boom 1!'));

// Добавляем второй обработчик
button.addEventListener('click', () => alert('Boom 2!'));

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

const button = document.getElementById('myButton');
const handler = () => alert('Boom 1!');
button.addEventListener('click', handler()); // вызывается обработчик

При необходимости, можно удалить обработчик:

const button = document.getElementById('myButton');

const handler = () => alert('Boom 1!');
button.addEventListener('click', handler);
// Важно что сюда передается ровно та же самая функция (по ссылке)
button.removeEventListener('click', handler);

В процессе выполнения обработчиков могут возникать новые события как от действий пользователя, так и программно, в самих обработчиках, а некоторые события всегда возникают целым блоком, например mouseup и click. Но это не означает, что выполнение кода сразу переключается на обработку этих событий. Вместо этого события складываются в очередь и выполняются строго последовательно.

Но некоторые события все же берутся в обработку сразу. Это касается тех событий, которые генерируются программно, например onfocus.

Возникает закономерный вопрос, что происходит со страницей во время выполнения обработчика? И здесь возможны варианты. Если обработчик выполняет некоторый код синхронно, например, занимается вычислениями, то в этот момент блокируется всё остальное и страница замирает (говорят "фризится").

Если такое поведение длится слишком долго, то некоторые браузеры зависают, а другие предлагают закрыть вкладку. Отсюда вывод, обработчики должны выполнять свою задачу максимально быстро. Если задача долгая и синхронная, то её можно вынести либо в webworker, либо разбить на этапы, фактически реализовав кооперативную многозадачность (основы операционных систем, в любой книге). Самый простой способ сделать это, периодически прерывать выполнение и продолжать выполнять через setTimeout(). Это позволит выполниться остальным событиям.

А что если задача асинхронная, например, выполняется запрос к серверу? В таком случае все продолжает прекрасно работать.

Вообще говоря, из этого урока должно стать понятно, почему js именно такой, какой есть. Событийная система возможна только в асинхронном коде. По сути, при загрузке страницы происходит инициализация и установка обработчиков, а дальше, как правило, не выполняется никакой код, вся страница находится в ожидании действий от пользователя.

Объект события

С каждым возникающим событием связана информация, зависящая от типа события. Например, событие click это не только факт сам по себе, но также и координаты точки на экране, где был совершен клик. Эта информация доступна через специальный объект-событие, передающийся в обработчик события. Такой объект всегда передается в любой обработчик как единственный параметр.

<div id="myElement">Бум!</div>
const button = document.getElementById('myElement');

button.addEventListener('click', (e) => {
  // Координаты точки, в которой произошел клик
  console.log(e.clientX);
  console.log(e.clientY);
});

Базовые свойства объекта-события Event:

  • event.target - элемент, на котором произошло событие
  • event.type - тип события

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

Действие по умолчанию

Для некоторых элементов, браузер выполняет действие по умолчанию при срабатывании определенных событий. Например, если повесить обработчик на клик по ссылке, то, выполнив этот клик, мы внезапно перейдем на другую страницу, ту, которая была указана в атрибуте href. Здесь мы видим пример того самого действия по умолчанию, на которое никак не влияет наличие обработчиков. Чтобы отменить это действие, а такое бывает нужно часто, необходимо вызвать метод event.preventDefault() внутри обработчика.

<a href="#" id="myElement">Бум!</a>
const button = document.getElementById('myElement');

button.addEventListener('click', (e) => {
  // Если этого не сделать, то браузер выполнит загрузку новой страницы
  e.preventDefault();
  alert(e.target.textContent);
});

Иногда в коде проектов встречаются инлайновые обработчики. В таком случае они записываются прямо в HTML:

<a href="#" id="myElement" onclick="alert('hey'); return false;">Бум!</a>

Возврат false внутри значения атрибута также приводит к отмене действия по умолчанию.

Действиями по умолчанию обладают следующие элементы:

  • Клик по ссылке приводит к переходу на страницу указанную в href атрибуте.
  • Клик на кнопку с типом submit начинает отправку формы на сервер.
  • Вращение колесом мышки в textarea передвигает текст, если он не помещается
  • Вызов контекстного меню с помощью правого клика мышки

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Для полного доступа к курсу нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
120
курсов
900
упражнения
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 120 курсов, 2000+ часов теории
  • 900 практических заданий в браузере
  • 360 000 студентов

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Фронтенд-разработчик
Профессия

Фронтенд-разработчик

Разработка фронтенд-компонентов веб-приложений
4 августа 8 месяцев

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».