JavaScript Events: Kingsman

для Epic Skills

События

mosaic browser

Браузер мамонта

Динамики - никакой.

О пользователе можно узнать только то, что он открыл страницу. Да и то из логов бэкэнда.

Браузер современного человека

opera neon

Тут тебе и плеер, и облако ссылок, и вертикальные табы, и встроенный мессенджер, и куча магии 🎩
Но главное: в современном браузере работает javascript. И позволяет обрабатывать поведение пользователя.
Позволяет следить за любой мелочью, видеть всё, слушать всё.
Словно шпион.

kingsman

Сегодня мы этим
воспользуемся

Сегодня мы
будем шпионами

Современный шпион может

должен следить

Но бездействующий шпион

почти мёртвый шпион

Что-то

пора менять

Паттерн «Наблюдатель»

Проблема: код реакции на пользователя и бизнес-логики сильно связан, при изменении одного приходится изменять и другой. Сложно реализовать связь один-ко-многим (я выпил кофе, надо внести изменения в кошелёк, в биохимическое состояние тела, в учёт лояльности кафе).

Решение: разнести источник и потребителей события. Источник не знает о подписчиках, но знает как известить их всех: он передаёт им событие

Паттерн?

Калька с pattern. Устойчивая идея для решения проблемы.

Описывает проблему, дополнительные условия, идею решения. Не конкретная реализация, один и тот же паттерн можно реализовать множеством способов.

Окей!

У посетителя сайта явно происходят какие-то события, а мы, как шпионы, можем их слушать, наблюдать.

Но как? Чем?

О каких событиях мы можем знать?

У хорошего шпиона

должно быть много инструментов

Арсенал событий

Разберёмся, что есть в арсенале хорошего шпиона.

Использование атрибута HTML

Обработчик может быть назначен прямо в разметке, в атрибуте, который называется on<событие>.

Аккуратнее с кавычками!
Этот способ устарел!

Использование свойства DOM-объекта

Ключевое слово this

Внутри обработчика события this ссылается на тот элемент, на котором он сработал. Это можно использовать, чтобы получить, изменить элемент и его свойства.

<button onclick="alert(this.innerHTML)">Жми</button> <button onclick="this.innerHTML='Нажми'">Жми</button>

Есть кое-что

что нужно
предотвращать

Предотвратить

Часто требуется отменять стандартное браузерное поведение.

      <a href="vk.com" id="fun">Уходи!</button>
      <script>
      document.getElementById('fun').onclick = function(event) {
        event.preventDefault();
      };
      </script>
    

Будь осторожен!
Не вставляй
остановки всюду

Внимательнее

Назначая обработчик через DOM-свойство, будьте осторожны. Нужно так elem.onclick = spy, а не так elem.onclick = spy()

А вот если назначаешь через атрибут, то всё наоборот. Вот так правильно: <button onclick="spy()">

Внимательнее

При использовании DOM-свойства важно понимать, что по клику должна выполниться функция.
Назначать так неправильно: elem.onclick = "spy()".
А вот так норм:

      elem.onclick = function() {
        spy();
      }
    

Пора

рулить

треки

А как вызвать много действий?

      <a href="vk.com" id="productivity">Важное решение!</button>
      <script>
      document.getElementById('productivity').onclick = kill;
      document.getElementById('productivity').onclick = increase;
      </script>
    

Какая функция выполнится при клике?
onclick позволяет назначать обработчики, а нам нужно их добавлять.
Нужен другой способ добавления обработчиков. Нужен новый герой!

Добавление множества обработчиков

addEventListener - добавляем обработчик

      <a href="vk.com" id="productivity">Важное решение!</button>
      <script>
      var productivity = document.getElementById('productivity');
      productivity.addEventListener('click', kill);
      productivity.addEventListener('click', increase);
      </script>
    

Удаляем обработчики

removeEventListener - удаляем обработчик
Удаление требует ту же функцию

      function kill(event) {
       event.preventDefault();
      }
      var productivity = document.getElementById('productivity');
      productivity.addEventListener('click', kill);
      productivity.removeEventListener('click', kill);
    

С такой же функцией - не сработает

Даже если код функций совпадает, обработчик не удалится.

      var productivity = document.getElementById('productivity');
      productivity.addEventListener('click', function(event) {
       event.preventDefault();
      });
      productivity.removeEventListener('click', function(event) {
       event.preventDefault();
      });
    

Особенности

События стилей (например, transitionend) можно отследить только при помощи addEventListener

В IE8- работает альтернативный метод attachEvent.

Что делать, когда событий много одновременно?

Порядок обработки событий

События могут возникать не только по очереди, но и «пачкой» по много сразу. Возможно и такое, что во время обработки одного события возникают другие, например пока выполнялся код для onclick – посетитель нажал кнопку на клавиатуре (событие keydown).

Очередь событий (main thread)

Когда происходит событие, оно попадает в очередь.

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

Клик: mousedown при прожимании клавиши мыши, mouseup при её отпускании. И click сразу с mouseup, ибо произошёл целый клик.

Синхронные события

Обычно возникающие события «становятся в очередь».

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

Пример

Асинхронные события

Есть хак, чтобы сделать события асинхронными: setTimeout(..., 0)

Пример

event

А что там внутри?

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

Каждый обработчик события передаёт объект события

      var productivity = document.getElementById('productivity');
      productivity.addEventListener('click', function(event) {
       console.log(event.type); // click
       console.log(event.target); // productivity
       console.log(event.target); // productivity
      });
    

Как посмотреть event в реальном времени?

Очень просто!

посмотрим

Всплытие

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

Не все события всплывают
например, focus не всплывает.

Проверить

target vs currentTarget

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

Предотвращение всплытия

На любом уровне всплытия можно указать, что событие полностью обработано, и остановить всплытие:

event.stopPropagation()

Погружение

Кроме «всплытия» событий, предусмотрено ещё и «погружение». Оно гораздо менее востребовано, но иногда, очень редко, знание о нём может быть полезным. Стандарт выделяет три стадии прохода события:

Погружение

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

Почитать