Обработчики событий

Начиная с этого занятия, мы подробно рассмотрим такую тему как события. Что такое событие? Это своего рода «сигнал» от браузера что, что-то произошло. Например:

  • пользователь кликает мышкой по HTML-странице (браузер генерирует событие onclick);
  • пользователь перемещает курсор мыши (браузер генерирует событие mousemove);
  • пользователь нажимает на клавишу клавиатуры (браузер генерирует события keydown/keyup);
  • когда полностью загружается HTML-документ браузер генерирует событие DOMContentLoaded).

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

В самом простом случае это можно сделать так. Предположим, у нас есть ссылка:

<!DOCTYPE html>
<html>
<head>
<title>Уроки по JavaScript</title>
</head>
<body>
<p><a id="link" href="#" onclick="showMessage()">Нажми меня</a>
<script>
</script>
</body>
</html>

и в ней указан атрибут onclick. Через этот атрибут происходит «привязка» функции showMessage к событию onclick. То есть, при щелчке мышью по ссылке будет вызвана функция showMessage. Сама же функция должна быть определена в скрипте, например, так:

function showMessage() {
    alert("Hello!");
}

Теперь, при обновлении документа и щелчке мышью по ссылке на экране браузера будет отображаться окно с сообщением «Hello!».

А можно ли программно применить обработчик для события onclick? Да, можно. Давайте для этого создадим еще один элемент:

<p><input type="button" value="Событие onclick" />

и в скрипте для тега <input> и свойству onclick присвоим ссылку на функцию-обработчик:

let inp = document.querySelector("input");
inp.onclick = showMessage;

Обратите внимание, мы здесь передаем ссылку на функцию, поэтому записываем ее без круглых скобок в конце. Если прописать вот так:

inp.onclick = showMessage();

то свойству onclick будет присвоен результат вызова функции, то есть, значение undefined и обработки события уже не будет.

Вот эти два способа (через атрибут onclick и свойство onclick) для назначения обработчиков, в действительности, работают одинаково: в обоих случаях при событии onclick соответствующего элемента будет вызвана функция showMessage. Но есть и отличия. При записи через атрибут происходит фактически следующее:

a.onclick = function(event) {
    showMessage();
}

то есть, свойству onclick присваивается ссылка на анонимную функцию с аргументом event (о нем мы поговорим позже) и в ней уже вызывается указанная функция showMessage. Именно поэтому в атрибуте onclick мы прописываем функцию showMessage() с круглыми скобками.

На практике используют оба способа для назначения обработчика какого-либо события. И в обоих случаях обработчик будет храниться в соответствующем свойстве (например, onclick, как в нашем скрипте).

Если необходимо сбросить обработчик и не реагировать, например, на событие onclick, то ему присваивается значение null:

inp.onclick = showMessage;
inp.onclick = null;

Теперь при нажатии на кнопку ничего не будет происходить.

В теле каждой функции-обработчика можно использовать ключевое слово this, которое ссылается на элемент DOM-дерева, на котором висит этот обработчик. Например, перепишем нашу функцию так:

function showMessage() {
    alert(this.tagName);
}

Нажимая на кнопку, получаем значение input, а при нажатии на ссылку – значение undefined. Почему у ссылки undefined? Дело в том, что обработчик ссылки прописан через атрибут onclick и вызов осуществляется из анонимной функции. В результате контекст this теряется и становится undefined. Исправить это можно несколькими способами. Ну, во-первых, назначить обработчик через свойство onclick:

link.onclick = showMessage;

А, во-вторых, использовать аргумент event анонимной функции для обращения к контексту:

function showMessage(event) {
    alert(event.currentTarget.tagName);
}

Здесь свойство currentTarget объекта event содержит ссылку на узел DOM-дерева, из которого пришло событие onclick. Что еще содержит объект event? У него имеются такие свойства:

  • event.type – тип события (например, для onclick будет содержать строку «click»);
  • event.clientX / event.clientY – координаты мыши в момент возникновения события (относительно границ окна браузера).

И ряд других в зависимости от типа события, о которых мы будем говорить на следующих занятиях.

У рассмотренных двух способов назначения обработчиков событий имеется одно существенное ограничение: мы не можем назначить несколько разных функций для одного и того же события. Тем не менее, иногда, в этом возникает необходимость. И разработчики языка JavaScript учли этот момент. Для назначения некоторому элементу (element) нескольких обработчиков для события event следует использовать метод

element.addEventListener(event, handler[, options]);

здесь event – строка с названием события; handler – ссылка на функцию-обработчик; options – необязательный параметр, о котором мы поговорим на последующих занятиях.

Итак, зададим два обработчика для события onclick конпки:

inp.addEventListener("click", showMessage);
inp.addEventListener("click", function(event) {
    console.log(event.clientX, event.clientY);
});

Теперь, при нажатии на кнопку мы увидим окно с сообщение «input» и в консоли координаты мыши в момент нажатия на кнопку.

Обратите внимание, если у нас будет задан еще один обработчик через свойство onclick:

inp.onclick = function() {
     console.log("обработчик onclick");
};

то он будет третьим. То есть, метод addEventListener не использует свойство onclick для назначения обработчиков, он реализует другой механизм на основе паттерна проектирования «слушатели». Поэтому на практике используют или метод addEventListener или свойство onclick, но не оба вместе.

Для удаления обработчиков, созданных с помощью addEventListener, используется метод

element.removeEventListener(event, handler[, options]);

Все аргументы здесь аналогичны аргументам метода addEventListener. Например, удалим обработчик showMessage тега input:

inp.removeEventListener("click", showMessage);

Теперь, при обновлении документа, мы увидим только сообщения в консоли. А вот удалить обработчик анонимной функции не получится, так как у нас нет ссылки на нее, а значит, мы не можем указать нужный handler у метода removeEventListener. Вот это следует иметь в виду при использовании анонимных функций в качестве обработчиков событий.

Также некоторым событиям можно назначать обработчики только с помощью метода addEventListener. Например, событию «transitionend», которое возникает при завершении CSS-анимации. Предположим, у нас имеется вот такой стиль:

<style>
input {
    transition: width 1s;
    width: 200px;
}
.expand { width: 400px; }
</style>

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

<p><input type="button" onclick="this.classList.toggle('expand')" value="Событие onclick" />

Далее, создаем обработчик события:

inp.addEventListener("transitionend", showMessage);

Теперь, при нажатии на кнопку, она постепенно увеличится и по окончании анимации вызовется метод showMessage.

В заключение этого занятия я приведу пример назначения объекта в качестве обработчика события. Да, так тоже можно делать и выглядит это следующим образом:

inp.addEventListener("click", {
    handleEvent(event) {
        console.log("Событие: " + event.type);
    }
});

Смотрите, здесь внутри объекта задан специальный метод  handleEvent, который автоматически вызывается, если объект назначен в качестве обработчика события. То есть, это стандартизированный метод, который должен присутствовать в объекте для обработки события.

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

class MyHandler {
    handleEvent(event) {
        console.log("Событие: " + event.type);
    }
}
 
let inp = document.querySelector("input");
inp.addEventListener("click", new MyHandler());

Вот так в JavaScript назначаются и удаляются обработчики событий.

Видео по теме