{"componentChunkName":"component---src-templates-post-js","path":"/javascript/kak-rabotaet-delegirovanie-sobityi-v-javascript/","result":{"pageContext":{"slug":"kak-rabotaet-delegirovanie-sobityi-v-javascript","content":"\n<p>Очень доходчивая статья о том, как работает делегирование событий в JavaScript и как не запутаться в слушателях событий.</p>\n\n\n\n<p class=\"author\"><em>Перевод&nbsp;</em><a href=\"https://medium.com/@bretdoucette/part-4-what-is-event-delegation-in-javascript-f5c8c0de2983\"><em>Part 4: What is Event Delegation in JavaScript?</em></a></p>\n\n\n\n<p id=\"e942\">Чтобы понять делегирование событий в JavaScript, вам сначала надо понять работу слушателей самих событий (ну или event listeners).</p>\n\n\n\n<p id=\"fb4e\">Я не очень люблю описывать вещи словами из их названия, но тут уж некуда деваться, слушатели событий следят за срабатыванием самих событий на странице. Вот, собственно и всё.</p>\n\n\n\n<p id=\"3c2b\">Событием в&nbsp;JavaScript можно назвать то, что “<em>происходит с HTML элементом</em>”, а происходить с ним может много чего.</p>\n\n\n\n<h2 id=\"c270\">Вот некоторые популярные JavaScript события:</h2>\n\n\n\n<p id=\"796c\"><code>change&nbsp;</code>— срабатывает тогда, когда что-то поменялось в HTML элементе.</p>\n\n\n\n<p id=\"9be2\"><code>click&nbsp;</code>— когда пользователь кликает на элемент.</p>\n\n\n\n<p id=\"8ac4\"><code>mouseover</code>&nbsp;— когда пользователь наводит мышь на HTML элемент.</p>\n\n\n\n<p id=\"72ca\"><code>mouseout</code>&nbsp;— когда пользователь отводит мышку от элемента.</p>\n\n\n\n<p id=\"f78d\"><code>keydown</code>&nbsp;— когда пользователь кликает на клавиатуру.</p>\n\n\n\n<p id=\"58ed\"><code>load&nbsp;</code>— когда браузер заканчивает загрузку страницы.</p>\n\n\n\n<p id=\"b757\"><code>addEventListener()</code></p>\n\n\n\n<p id=\"fad0\">Чтобы добавить слушатель событий к HTML элементу, вы можете использовать&nbsp;<code>addEventListner()</code>&nbsp;метод.</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>Пример addEventListener()\n\nconst character = document.getElementById(\"disney-character\");\ncharacter.addEventListener('click', showCharactersName);</code></pre>\n\n\n\n<p id=\"28e8\">Первая часть кода это то, что мы будем слушать, в нашем случае это просто HTML элемент. Но в JavaScript можно слушать просто неописуемое изобилие вещей на странице и не только. Это может быть HTML элемент на странице, это может быть сам документ или это может быть окно браузера. В общем, это объект высшего порядка в client-side JavaScript который охватывает почти всё. Но всё же, в большинстве случаев это HTML элементы. Вторая часть кода это уже сам слушатель.</p>\n\n\n\n<h3 id=\"466e\">Вот как работает&nbsp;<code>eventListener</code>:</h3>\n\n\n\n<p id=\"3321\">Когда пользователь кликает на HTML элемент, с&nbsp;<code>id</code>&nbsp;<code>disnay-character</code>, то слушатель событий срабатывает и вызывает функцию&nbsp;<code>showCharactersName</code>.</p>\n\n\n\n<p id=\"7bf6\">Слушатели событий выставляются после загрузки страницы. Так что, когда вы впервые открываете сайт, браузер скачивает, считывает и выполняет JavaScript.</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>const character = document.getElementById(\"disney-character\");\n\ncharacter.addEventListener('click', showCharactersName);</code></pre>\n\n\n\n<p id=\"95fe\">В коде выше, при загрузке страницы, слушатель событий находит HTML элемент с&nbsp;<code>id</code>&nbsp;<code>disnay-character</code>&nbsp;и выставляет на него слушатель события по клику.</p>\n\n\n\n<p id=\"8858\">Вся эта конструкция будет успешно работать, если элемент уже существует на странице. Но что происходит со слушателем событий, когда элемент добавляется в DOM уже после загрузки страницы?</p>\n\n\n\n<h2 id=\"7cb5\"><strong>Делегирование событий</strong></h2>\n\n\n\n<p id=\"ccbb\">Именно оно решает эту проблему. Чтобы понять принцип его работы, нам надо посмотреть ниже на список персонажей Disney.</p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"446\" height=\"457\" src=\"https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_OciPJyDlVzbNeYPNBfTIkA.png\" alt=\"\" class=\"wp-image-212\" srcset=\"https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_OciPJyDlVzbNeYPNBfTIkA.png 446w, https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_OciPJyDlVzbNeYPNBfTIkA-293x300.png 293w\" sizes=\"(max-width: 446px) 100vw, 446px\" /></figure>\n\n\n\n<p id=\"14a1\">У этого списка есть довольно простой функционал. Именно для наших нужд мы можем добавить несколько персонажей в этот список и проверить боксы рядом с именем персонажа.</p>\n\n\n\n<p id=\"fa3b\">Этот список также является динамическим. Инпуты (Mickey, Minnie, Goofy) были добавлены уже&nbsp;<strong>ПОСЛЕ</strong>&nbsp;загрузки страницы и следовательно, на них не были прикреплены слушатели событий.</p>\n\n\n\n<p id=\"e7fd\">Давайте посмотрим на этот код:</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>const checkBoxes = document.querySelectorAll('input');\n\ncheckBoxes.forEach(input => input.addEventListener('click', ()=> alert('hi!')));\n\n//Должно выскочить окно при клике на один из инпутов(Mickey, Minnie, или Goofy)</code></pre>\n\n\n\n<p>Но давайте посмотрим на HTML при загрузке страницы:</p>\n\n\n\n<pre class=\"wp-block-code html\"><code>&lt;ul class=\"characters\">&lt;/ul></code></pre>\n\n\n\n<p>А теперь давайте взглянем на HTML после загрузки страницы (из локального веб-хранилища, API запроса и т.п.):</p>\n\n\n\n<pre class=\"wp-block-code html\"><code>&lt;ul class=\"characters\">\n &lt;li>\n   &lt;input type=\"checkbox\" data-index=\"0\" id=\"item0\">\n   &lt;label for=\"item0\">Mickey&lt;/label>\n &lt;/li>\n \n &lt;li>\n   &lt;input type=\"checkbox\" data-index=\"1\" id=\"item1\">\n   &lt;label for=\"item1\">Minnie&lt;/label>\n &lt;/li>\n \n &lt;li>\n   &lt;input type=\"checkbox\" data-index=\"2\" id=\"item2\">\n   &lt;label for=\"item2\">Goofy&lt;/label>\n &lt;/li>\n&lt;/ul>\n** Инпуты были добавлены в DOM после загрузки страницы и на них не висят слушатели событий.</code></pre>\n\n\n\n<p>Если вы захотите кликнуть на инпуты&nbsp;персонажей — Mickey, Minnie, or Goody), то вы наверное ожидали бы увидеть всплывающее окно с надписью “hi!”, но так как они не были загружены на страницу при её инициализации, то и прослушиватели событий&nbsp;<strong>НЕ БЫЛИ</strong>&nbsp;добавлены на эти элементы и само собой ничего не произойдёт.</p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"284\" height=\"265\" src=\"https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_zzKjANG7YP9bnoDhqsm9ew.gif\" alt=\"\" class=\"wp-image-213\"/></figure>\n\n\n\n<h2 id=\"98d2\">Как же пофиксить эту проблему?</h2>\n\n\n\n<p id=\"03f2\">Делегированием событий.</p>\n\n\n\n<p id=\"b903\">Суть делегирования событий в том, что вместо прослушивания изменений на инпутах напрямую, нам нужно прослушивать HTML элемент, который был бы на странице при изначальной загрузке.</p>\n\n\n\n<p id=\"760a\">В нашем примере это список&nbsp;<code>ul</code>&nbsp;с классом&nbsp;<code>characters</code>, который появляется при загрузке страницы. Мы можем повесить слушатель событий прямо на него.</p>\n\n\n\n<pre class=\"wp-block-code html\"><code>&lt;ul class=\"characters\"> // Родитель - всегда на странице\n &lt;li>\n   &lt;input type=\"checkbox\" data-index=\"0\" id=\"char0\"> //ПОТОМОК 1\n   &lt;label for=\"char0\">Mickey&lt;/label>\n &lt;/li>\n \n &lt;li>\n   &lt;input type=\"checkbox\" data-index=\"1\" id=\"char1\"> //ПОТОМОК 2\n   &lt;label for=\"char1\">Minnie&lt;/label>\n &lt;/li>\n \n &lt;li>\n   &lt;input type=\"checkbox\" data-index=\"2\" id=\"char2\"> //ПОТОМОК 3\n   &lt;label for=\"char2\">Goofy&lt;/label>\n &lt;/li>\n&lt;/ul></code></pre>\n\n\n\n<p id=\"9e85\">Лучше рассматривать делегирование событий, как ответственных родителей и нерадивых детишек. В этом случае родители это просто боги, а детям приходиться слушать всё, что говорят их родители. Прекрасный аспект тут в том, что если бы мы добавили больше детей (или инпутов), то родители бы остались такими же в правах — они были с самого начала или другими словами, прямо с загрузки страницы.</p>\n\n\n\n<p id=\"13a8\">Давайте прикрепим слушатель событий:</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>&lt;ul class=\"characters\">\n&lt;/ul>\n&lt;script>\n  function toggleDone (event) {\n    console.log(event.target)\n  } \n  const characterList = document.querySelector('.characters')\n  characterList.addEventListener('click', toggleDone)\n&lt;/script></code></pre>\n\n\n\n<p>Теперь у нас есть слушатель событий на&nbsp;<code>ul</code>, а не на каждом отдельном потомке. Так что же тогда произойдет, если мы кликнем на инпут после того, как загрузилась страница и выведем в консоль&nbsp;<code>event.target</code>?</p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"700\" height=\"88\" src=\"https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_0vj3yxDmqI-a1_s-rB3ETA.png\" alt=\"\" class=\"wp-image-214\" srcset=\"https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_0vj3yxDmqI-a1_s-rB3ETA.png 700w, https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_0vj3yxDmqI-a1_s-rB3ETA-300x38.png 300w\" sizes=\"(max-width: 700px) 100vw, 700px\" /></figure>\n\n\n\n<p id=\"dfb0\"><code>event.target</code>&nbsp;это отсылка к объекту, на котом сработало событие. Или другими словами, он указывает на HTML элемент на котором сработало событие.</p>\n\n\n\n<p id=\"89ec\">Событие в нашем случае это клик. Объект на котором отработало событие это&nbsp;<code>&lt;input/&gt;</code>.</p>\n\n\n\n<p id=\"f337\">**&nbsp;<code>label</code>&nbsp;рассматривается, как часть объекта&nbsp;<code>input</code>&nbsp;— поэтому мы видимо их обоих**</p>\n\n\n\n<h2 id=\"16c5\">Console.log(event.currentTarget)</h2>\n\n\n\n<p id=\"a45b\">Если мы выведем в консоль&nbsp;<code>event.currentTarget</code>&nbsp;— то увидим мы кое что другое.</p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"700\" height=\"146\" src=\"https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_j2UgR5rfWILSuahqs4eMvg.png\" alt=\"\" class=\"wp-image-215\" srcset=\"https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_j2UgR5rfWILSuahqs4eMvg.png 700w, https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_j2UgR5rfWILSuahqs4eMvg-300x63.png 300w\" sizes=\"(max-width: 700px) 100vw, 700px\" /></figure>\n\n\n\n<p><code>event.currentTarget</code>&nbsp;указывает на данную цель события, так как сам ивент уходит дальше в DOM. Он всегда ссылается на элемент к которому был прикреплен&nbsp;<code>eventListener</code>. В нашем случае этим элементом являлся неупорядоченный список&nbsp;<code>characters</code>, так что это то, что мы увидим в консоле.</p>\n\n\n\n<h2 id=\"c264\">Пишем делегирование событий в JavaScript</h2>\n\n\n\n<p id=\"31f4\">Так как теперь мы знаем, что&nbsp;<code>event.target</code>&nbsp;указывает на элементы на которых отработало событие и также мы знаем, какой элемент нам нужно слушать, решение проблемы с делегированием для нас не является проблемой.</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>//делегирование событий\nfunction toggleDone (event) {\n  if (!event.target.matches('input')) return\n  console.log(event.target)\n  //Теперь мы выбрали нужный инпут и можем начать манипуляции с DOM\n}</code></pre>\n\n\n\n<p id=\"eeef\">В общем, код выше говорит нам то, что если элемент на котором отработал клик не является инпутом, то мы выходим из функции.</p>\n\n\n\n<p id=\"bb06\">А если этот элемент является инпутом, то тогда мы выводим в консоль&nbsp;<code>event.target</code>&nbsp;и выполняем последующий JavaScript код на этом узле потомке.</p>\n\n\n\n<p id=\"713a\">Очень важно, что теперь мы можем быть уверены в том, что пользователь кликнул нужный узел потомок, пусть даже инпуты не были добавлены в DOM после начальной загрузки страницы.</p>\n\n\n\n<h2 id=\"8bf9\">Event Bubbling (Всплытие событий)</h2>\n\n\n\n<p id=\"a5ca\">Если вы хотите завершить чтение на этом моменте — то смело это делайте. Мы уже узнали основы делегирования событий. Но для глубокого понимания того, почему оно работает, нам нужно понять&nbsp;<strong><em>Event Bubbling</em></strong>.</p>\n\n\n\n<h3 id=\"a792\"><strong>Что происходит на самом деле при клике?</strong></h3>\n\n\n\n<p id=\"6353\">Всякий раз, когда пользователь кликает, этот самый клик отдаётся вверх на самую высь DOM и отрабатывает событие клика на всех элементах родителя по которому был сделан клик. Вы не всегда видите эти клики, так как вы не всегда слушаете (с&nbsp;<code>eventListener</code>) клики на этих элементах, но как бы то ни было, но такое “всплытие” действий имеет место быть.</p>\n\n\n\n<p id=\"e8a1\">Это называется&nbsp;<em>Event Bubbling (Всплытие событий)</em>&nbsp;или распространением события.</p>\n\n\n\n<p id=\"665e\">Это означает то, что всякий раз, когда вы кликаете на один из наших инпутов в DOM, вы по сути прокликиваете весь документ.</p>\n\n\n\n<p id=\"7412\">Вот пример:</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>&lt;div class=\"one\">\n  &lt;div class=\"two\">\n    &lt;div class=\"three\">\n    &lt;/div>\n  &lt;/div>\n&lt;/div>\n&lt;script>\n  const divs = document.querySelectorAll('div');  \n  function logClassName(event) {\n    console.log(this.classList.value);\n  }\n  divs.forEach(div => div.addEventListener('click', logClassName));\n&lt;/script></code></pre>\n\n\n\n<p>Выше у нас есть три дива: DIV #1, DIV #2, DIV #3. У каждого&nbsp;<code>div</code>’а есть свой прослушиватель событий и когда мы на него кликаем в браузере, то мы выводим в консоль имя класса, через функцию&nbsp;<code>logClassName()</code>.</p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"700\" height=\"323\" src=\"https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_NwINqHz873kEH_2D9iX0ng.gif\" alt=\"\" class=\"wp-image-216\"/></figure>\n\n\n\n<p>Выше мы видим то, что мы бы увидели в браузере. Обратите внимание на то, как мышка кликает по третьему&nbsp;<code>div</code>’у. Как и ожидалось, когда я кликаю по нему, я вижу его класс в консоле. Но кликая по div #3, я также кликаю и по div #2 и div#1, который вывелись в консоль. Это и называется всплытием событий. Мы видим каждый класс, потому что мы добавили прослушиватель событий каждому родительскому <code>div</code>’у.</p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"392\" height=\"202\" src=\"https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_txz1UUG5qc8b_B6oyClgwQ.png\" alt=\"\" class=\"wp-image-217\" srcset=\"https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_txz1UUG5qc8b_B6oyClgwQ.png 392w, https://stasonmars.ru/wp/wp-content/uploads/2021/02/1_txz1UUG5qc8b_B6oyClgwQ-300x155.png 300w\" sizes=\"(max-width: 392px) 100vw, 392px\" /></figure>\n\n\n\n<p>Но вернемся к нашему примеру с делегированием события:</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>&lt;ul class=\"characters\"> // Родитель -- где и находится слушатель события!\n &lt;li>\n   &lt;input type=\"checkbox\" data-index=\"0\" id=\"char0\"> //ПОТОМОК 1\n   &lt;label for=\"char0\">Mickey&lt;/label>\n &lt;/li>\n \n &lt;li>\n   &lt;input type=\"checkbox\" data-index=\"1\" id=\"char1\"> //ПОТОМОК 2\n   &lt;label for=\"char1\">Minnie&lt;/label>\n &lt;/li>\n \n &lt;li>\n   &lt;input type=\"checkbox\" data-index=\"2\" id=\"char2\"> //ПОТОМОК 3\n   &lt;label for=\"char2\">Goofy&lt;/label>\n &lt;/li>\n&lt;/ul>\n&lt;script>\n  const characterList = document.querySelector('.characters');\n  characterList.addEventListener('click', toggleDone);\n&lt;/script></code></pre>\n\n\n\n<p id=\"a7b4\">Вернёмся к нашему примеру с делегированием событий — у нас был только один слушатель события и он был выставлен на неупорядоченный список с классом&nbsp;<code>characters</code>. Тем не менее, мы кликнули на потомка этого родительского HTML элемента, элемент input, который запустил прослушиватель событий, который привязали к списку.</p>\n\n\n\n<p id=\"7b98\">В силу наличия всплытия событий, вы можете повесить&nbsp;<code>eventListener</code>&nbsp;на одном родительском HTML элементе, который находится выше потомков и этот прослушиватель событий будет срабатывать всякий раз, как заданное событие случиться на любом из его дочерних элементов — даже если они были добавлены после первоначальной загрузки страницы.</p>\n\n\n\n<h2 id=\"8cfb\">В завершение. А зачем использовать делегирование событий?</h2>\n\n\n\n<p id=\"9394\">Без делегирования событий, вам бы пришлось перепривязывать событие по клику на каждый новый&nbsp;<code>input</code>, добавленный на страницу. Делать такую писанину довольно проблематично и обременительно. Во первых, такой подход значительно увеличит количество слушателей событий на вашей странице и их большое количество увеличит объем памяти, потребляемое вашей страницей. Что приведет к снижению производительности, что само по себе уже плохо. Во вторых, могут произойти проблемы с утечкой памяти из-за циклической привязки и отвязки слушателей событий, и удаления элементов из DOM. Но это уже выходит за рамки данной статьи.</p>\n","type":"js","date":"2021-02-10T14:37:38.000Z","acf":{"seo_desc":"Очень доходчивая статья о том, как работает делегирование событий в JavaScript и как не запутаться в слушателях событий.","keywords":"делегирование событий javascript, делегирование событий js, event delegation javascript","pub_date":"2019-09-24","tags":"JavaScript, делегирование событий, события"},"title":"Делегирование событий в JavaScript","id":"7c43032e-ea51-587a-918b-8a514ff1854d","excerpt":"<p>Очень доходчивая статья о том, как работает делегирование событий в JavaScript и как не запутаться в слушателях событий.</p>\n","featured_media":{"source_url":"https://stasonmars.ru/wp/wp-content/uploads/2021/02/eventdelegatlf.jpg"},"interesting":[{"title":"Делегирование событий в JavaScript","slug":"kak-rabotaet-delegirovanie-sobityi-v-javascript","type":"js"}]}},"staticQueryHashes":["3576131997","63159454"]}