- DOM — Метод addEventListener
- Добавление обработчика событий к объекту window
- Передача параметров
- Всплытие или перехват события?
- Метод removeEventListener()
- Поддержка браузерами
- Мы забыли про делегирование в JavaScript. Event delegation in React
- Событие
- Распространение событий
- Всплытие событий
- Итак, переходим к обработке событий
- И тут к нам на помощь приходит делегирование событий (event delegation).
- А теперь что насчёт React
DOM — Метод addEventListener
Добавим обработчик события, который срабатывает при нажатии пользователем на кнопку:
document.getElementById("myBtn").addEventListener("click", displayDate);
Метод addEventListener() присоединяет обработчик события к определенному элементу. При этом новый обработчик события не переписывает уже существующие обработчики событий.
Таким образом, вы можете добавлять сколько угодно обработчиков событий к одному элементу. При этом это могут быть обработчики событий одного типа, например, два события нажатия мышкой.
Вы можете добавлять обработчики событий к любому объекту DOM, а не только к HTML элементам, например, к объекту окна.
Метод addEventListener() позволяет легко контролировать то, как обработчик реагирует на, так называемое, «всплывание» события.
Когда используется метод addEventListener(), JavaScript отделяется от разметки HTML, что улучшает читаемость скрипта и позволяет добавлять обработчики событий даже тогда, когда вы не можете контролировать разметку HTML.
Чтобы удалить обработчик события, нужно воспользоваться методом removeEventListener().
элемент.addEventListener(событие, функция, useCapture);
Первый параметр — тип события (например, «click» или «mousedown»).
Второй параметр — функция, которая будет вызываться при возникновении события.
Третий параметр — логическое значение (true/false), определяющее следует ли отправить событие дальше («всплывание») или нужно закрыть это событие. Этот параметр необязателен.
Обратите внимание, что в имени события не используется префикс «on» — «click» вместо «onclick».
В следующем примере при нажатии пользователем на элемент появляется окно с сообщением «Hello World!»:
элемент.addEventListener("click", function()< alert("Hello World!"); >);
Также, можно задать и внешнюю «именованную» функцию:
элемент.addEventListener("click", myFunction); function myFunction()
Метод addEventListener() позволяет добавлять несколько обработчиков событий к одному и тому же элементу не переписывая уже существующие обработчики событий:
элемент.addEventListener("click", myFunction); элемент.addEventListener("click", mySecondFunction);
Также, можно добавлять обработчики событий разных типов:
элемент.addEventListener("mouseover", myFunction); элемент.addEventListener("click", mySecondFunction); элемент.addEventListener("mouseout", myThirdFunction);
Добавление обработчика событий к объекту window
Метод addEventListener() позволяет добавлять обработчики событий к любому объекту HTML DOM — HTML элементам, HTML документу, объекту окна (объект window) и другим объектам, поддерживающим события как объект xmlHttpRequest.
В следующем примере добавляется обработчик события, который срабатывает, когда пользователь изменяет размер окна браузера:
window.addEventListener("resize", function()< document.getElementById("demo").innerHTML = "какой-то текст"; >);
Передача параметров
Если необходимо передать параметры, то используйте «анонимную» функцию, которая вызывает специализированную функцию с параметрами:
элемент.addEventListener("click", function()< myFunction(p1, p2); >);
Всплытие или перехват события?
В HTML DOM существует два способа распространения события — всплытие и перехват.
Распространение события — это последовательность обработки события HTML элементами. Если у вас есть элемент
, вложенный в элемент , и пользователь мышкой нажимает на элемент
, то какой элемент должен обработать событие «click» первым?
При всплытии первым обрабатывает событие самый вложенный элемент, затем его родитель и т.д.: таким образом сначала обрабатывать событие «click» будет элемент
, а затем элемент .
При перехвате все происходит наоборот — сначала событие обрабатывает самый внешний элемент, в нашем случае , а затем вложенный, т. е. элемент
.
Метод addEventListener() позволяет задавать тип распространения события. Это можно сделать при помощи параметра «useCapture«:
addEventListener(событие, функция, useCapture );
По умолчанию этот параметр имеет значение false, что задает всплытие события. Если задать ему значение true, то будет использоваться перехват.
document.getElementById("myP").addEventListener("click", myFunction, true); document.getElementById("myDiv").addEventListener("click", myFunction, true);
Метод removeEventListener()
Метод removeEventListener() удаляет обработчик события, подключенный методом addEventListener():
элемент.removeEventListener("mousemove", myFunction);
Поддержка браузерами
Методы addEventListener() и removeEventListener() в настоящее время поддерживаются всеми основными браузерами.
Однако, IE 8 и более ранних версий, а также Opera 6.0 и более ранних версий не поддерживают методы addEventListener() и removeEventListener(). Тем не менее, для этих версий браузеров можно использовать метод attachEvent() для прикрепления обработчика события и метод detachEvent() для его удаления:
элемент.attachEvent(событие, функция);
элемент.detachEvent(событие, функция);
Пример кросс-браузерного решения:
var x = document.getElementById("myBtn"); if (x.addEventListener) < // для всех основных браузеров x.addEventListener("click", myFunction); >else if (x.attachEvent) < // для IE 8 и более ранних версий x.attachEvent("onclick", myFunction); >
Мы забыли про делегирование в JavaScript. Event delegation in React
И в конце: почему не надо забывать об делегировании в React.
Событие
JavaScript с HTML взаимодействуют между собой за счёт событий (events). Каждое событие служит для того, чтобы сказать JavaScript’у о том, что в документе или окне браузера что-то произошло. Для того чтобы отловить эти события нам нужны слушатели (listeners), этакие обработчики, которые запускаются в случае возникновения события.
Распространение событий
Порядок. Решая проблему: как понять, какой части страницы принадлежит событие? Было реализовано два способа: в Internet Explorer — “всплытие событий”, а в Netscape Communicator — “перехват событий”.
Всплытие событий
В данном случае событие срабатывает у самого глубокого узла в дереве документа, после поднимается по иерархии до самого window.
В этом случае будет такой порядок:
В случае с перехватом событий работает все наоборот:
Задумывалось что событие можно будет обработать до того, как оно достигло целевого элемента (так решили в Netscape позже подхватили все современные браузеры).
В итоге мы имеем такую структуру распространения DOM-событий:
- window
- document
- элемент html
- элемент body // заканчивается фаза перехвата
- элемент div // целевая фаза
- элемент body // начинается фаза всплытия
- элемент html
- document
- window
Делится эта схема на три фазы: фаза перехвата — событие можно перехватить до попадания на элемент, фаза цели — обработка целевым элементом и фаза всплытия — что бы выполнить какие-либо заключительные действия в ответ на событие.
Итак, переходим к обработке событий
Посмотрим типичный пример обработки события в JavaScript.
const btn = document.getElementById('myDiv') btn.addEventListener("click", handler) // some code btn.removeEventListener("click", handler)
Все бы нечего, но тут мы вспоминаем про наш любимы IE который подписывается на события с помощью attachEvent, а для удаления detachEvent. А еще можно подписываться на событие несколько раз. И не забываем что подписавшись анонимной функцией мы не имеем возможность отписаться.
Но мы же не г*внокодеры. Сделаем все по канону:
var EventUtil = < addHandler: function (elem, type, handler) < if (elem.addEventListener) < elem.addEventListener(type, handler, false) >else if (elem.attachEvent) < elem.attachEvent("on" + type, handler) >else < elem["on" = type] = hendler >>, removeHandler: function (elem, type, handler) < if (elem.removeEventListener) < elem.removeEventListener(type, handler, false) >else if (elem.detachEvent) < elem.detachEvent("on" + type, handler) >else < elem["on" = type] = null >> >
Так хорошо, а как же объект event? Ведь в IE нет .target есть .srcElement, preventDefault? нет returnValue = false. Но нечего добавим пару методов:
var EventUtil = < addHandler: function (elem, type, handler) < if (elem.addEventListener) < elem.addEventListener(type, handler, false) >else if (elem.attachEvent) < elem.attachEvent("on" + type, handler) >else < elem["on" = type] = hendler >>, getEvent: function (event) < return event ? event : window.event >, getTarget: function (event) < return event.target || event.srcElement >, preventDefault: function (event) < if (event.preventDefault) < event.preventDefault() >else < event.returnValue = false >>, removeHandler: function (elem, type, handler) < if (elem.removeEventListener) < elem.removeEventListener(type, handler, false) >else if (elem.detachEvent) < elem.detachEvent("on" + type, handler) >else < elem["on" = type] = null >>, stopPropagation: function (event) < if (event.stopPropagation) < event.stopPropagation() >else < event.cancelBubble = true >> >
И т.д. и т.п. и вот эти все танцы.
Хорошо мы молодцы, все проблемы решили, все ок. Правда код вышел довольно громоздким. А теперь представим, что нам нужно много подписок на множество элементов. Ух это займет не мало строк кода. Пример:
И так для каждого элемента, и надо удаление не забыть, работа с таргет и тому подобное
И тут к нам на помощь приходит делегирование событий (event delegation).
Все что нам надо это подключить один единственный обработчик к наивысшей точке в DOM-дереве:
В итоге у нас только один обработчик в памяти, а для нужного действия можно использовать свойство id. Меньшее потребление памяти повышает общее быстродействие страницы в целом. Для регистрации обработчика событий требуется меньше времени и меньше обращений к DOM. Исключение разве что mouseover и mouseout, с ними все немного сложнее.
А теперь что насчёт React
Все что касается кросcбраузерности за нас уже все сделали ребята из facebook. Все наши обработчики событий получают экземпляр SyntheticEvent. Который заботится о нас повторно используя события из пула удаляя все свойства после вызова обработчика.
Тем не менее лишний обработчик есть лишний обработчик. Несколько раз встречал, да и каюсь сам писал, такого рода код:
class Example extends React.Component < handleClick () < console.log('click') >render () < return ( // elem.id // elem.id onClick= console.log('click')> /> )> ) > > В примере показан случай, когда есть какой-то лист с n-количеством элементов, а значит и с n-количеством регистраций обработчиков.
Запустим зайдем на страницу и проверим сколько обработчиков сейчас в деле. Для этого я нашёл не плохой скрипт:
Array.from(document.querySelectorAll('*')) .reduce(function(pre, dom)< var clks = getEventListeners(dom).click; pre += clks ? clks.length || 0 : 0; return pre >, 0)
А теперь делегируем все это родительскому div элементу и ура, мы только что оптимизировали наше приложение в n=array.length раз. Пример код ниже:
class Example extends React.Component < constructor () < super() this.state = < useElem: 0 >> handleClick (elem) < var this.setState(< useElem: id >) > render () < return ( > // elem.id // elem.id useElem= /> )> ) > > Делегирование хороший инструмент для обработки большого количества подписок, а в случае с динамичным рендером и частых перерисовок просто незаменим. Пожалейте ресурсы пользователя, они не безграничны.
Статья написана на основе книги JavaScript для профессиональных веб-разработчиков, автор: Николас Закас.
Спасибо большое за внимание. Если есть чем поделится или нашли какой-то недочет, может ошибку или просто есть вопрос, то пишите в комментариях. Буду рад любой обратной связи!