что такое делегирование событий
Делегирование событий
Всплытие и перехват событий позволяет реализовать один из самых важных приёмов разработки – делегирование.
Идея в том, что если у нас есть много элементов, события на которых нужно обрабатывать похожим образом, то вместо того, чтобы назначать обработчик каждому, мы ставим один обработчик на их общего предка.
Рассмотрим пример – диаграмму Ба-Гуа. Это таблица, отражающая древнюю китайскую философию.
Её HTML (схематично):
В этой таблице всего 9 ячеек, но могло бы быть и 99, и даже 9999, не важно.
Наша задача – реализовать подсветку ячейки
Вместо того, чтобы назначать обработчик onclick для каждой ячейки
из таблицы динамически в любое время, и подсветка будет стабильно работать. Однако, у текущей версии кода есть недостаток. Клик может быть не на теге | , а внутри него. В нашем случае, если взглянуть на HTML-код таблицы внимательно, видно, что ячейка | содержит вложенные теги, например : Внутри обработчика table.onclick мы должны по event.target разобраться, был клик внутри или нет. | Вот улучшенный код: В итоге мы получили короткий код подсветки, быстрый и эффективный, которому совершенно не важно, сколько всего в таблице . | Применение делегирования: действия в разметкеЕсть и другие применения делегирования. Первое, что может прийти в голову – это найти каждую кнопку и назначить ей свой обработчик среди методов объекта. Но существует более элегантное решение. Мы можем добавить один обработчик для всего меню и атрибуты data-action для каждой кнопки в соответствии с методами, которые они вызывают: Обработчик считывает содержимое атрибута и выполняет метод. Взгляните на рабочий пример: Так что же даёт нам здесь делегирование? Приём проектирования «поведение»Делегирование событий можно использовать для добавления элементам «поведения» (behavior), декларативно задавая хитрые обработчики установкой специальных HTML-атрибутов и классов. Приём проектирования «поведение» состоит из двух частей: Поведение: «Счётчик»Например, здесь HTML-атрибут data-counter добавляет кнопкам поведение: «увеличить значение при клике»: Если нажать на кнопку – значение увеличится. Конечно, нам важны не счётчики, а общий подход, который здесь продемонстрирован. Элементов с атрибутом data-counter может быть сколько угодно. Новые могут добавляться в HTML-код в любой момент. При помощи делегирования мы фактически добавили новый «псевдостандартный» атрибут в HTML, который добавляет элементу новую возможность («поведение»). Поведение: «Переключатель» (Toggler)Ещё один пример поведения. Сделаем так, что при клике на элемент с атрибутом data-toggle-id будет скрываться/показываться элемент с заданным id : Это бывает очень удобно – не нужно писать JavaScript-код для каждого элемента, который должен так себя вести. Просто используем поведение. Обработчики на уровне документа сделают это возможным для элемента в любом месте страницы. Мы можем комбинировать несколько вариантов поведения на одном элементе. Шаблон «поведение» может служить альтернативой для фрагментов JS-кода в вёрстке. ИтогоДелегирование событий – это здорово! Пожалуй, это один из самых полезных приёмов для работы с DOM. Он часто используется, если есть много элементов, обработка которых очень схожа, но не только для этого. Конечно, у делегирования событий есть свои ограничения: ЗадачиСпрячьте сообщения с помощью делегированияВ результате должно работать вот так: P.S. Используйте делегирование событий. Должен быть лишь один обработчик на элементе-контейнере для всего. Раскрывающееся деревоСоздайте дерево, которое по клику на заголовок скрывает-показывает потомков: Решение состоит из двух шагов: Сортируемая таблицаСделать таблицу сортируемой: при клике на элемент строки таблицы должны сортироваться по соответствующему столбцу. | Каждый элемент имеет атрибут data-type: | В примере выше первый столбец содержит числа, а второй – строки. Функция сортировки должна это учитывать, ведь числа сортируются иначе, чем строки. P.S. Таблица может быть большой, с любым числом строк и столбцов. Поведение «подсказка»Напишите JS-код, реализующий поведение «подсказка». Пример HTML с подсказками: Результат в ифрейме с документом: В этой задаче мы полагаем, что во всех элементах с атрибутом data-tooltip – только текст. То есть, в них нет вложенных тегов (пока). Для решения вам понадобятся два события: После реализации поведения – люди, даже не знакомые с JavaScript смогут добавлять подсказки к элементам. Делегирование событий в JSНа прошлом уроке мы научились вешать обработчик события на одну кнопку в JS. Однако методы, хорошо работающие с единичными объектами, совершенно не эффективны для массовых манипуляций. Пример из реальной жизни: Директор школы не подходит к каждому новому ученику, чтобы сообщить ему о школьных правилах. Эту почетную миссию он делегирует классному руководителю, чтобы каждый новый ученик знал общие правила. На странице имеется какое-то количество навигационных ссылок, на которых при клике будут срабатывать одинаковые события. Навесить на каждую кнопку по событию не так и сложно. Но есть одна проблема. А что если пользователь сайта (заказчик) добавит через админку новую ссылку. У добавленной ссылки уже не будет события. Делегирование событий обезопасит вас от подобных косяков. Правильно будет работать сразу с родительским элементом, а не с каждым дочерним по отдельности. Тогда каждый новый добавленный элемент в родительский блок, автоматически унаследует и его событие. Пример с делегированием в JSПеред нами обычный список, где теги li являются дочерними для тега ul. Мы будем работать с родителем всех li. let ul = document.querySelector(‘ul’); Навешиваем обработчик события addEventListener на родителя и первым параметром передаем событие клик (можно любое событие). Вторым параметром передаем callback функцию с передаваемым объектом события (event), чтобы свойства объекта могли вызываться внутри, функции и выведем их в консоль. Нас интересует, как в DOM прописано значение у свойства tagName. ul.addEventListener(‘click’, function(event) < Свойства выведутся в консоль при клике по списку. Раскрываем всю эту цепочку MouseEvent → target → tagName. Дословный перевод: событие мыши → цель → имя тега. Начинающие разработчики часто заглядывают в консоль, поскольку визуализируя процесс, проще понять смысл происходящего и увидеть ошибки. Суть делегирования событийНам нужно проверить, куда кликнул пользователь, если он кликнул по списку, то выполнится какое-то действие. if(event.target && event.target.tagName == ‘LI’) < Нас не интересуют только клики по пунктам списка. Если целью события клика event, оказался тег LI, то в консоль выведется слово Привет! В первой части условия event.target мы проверяем, есть ли у объекта события свойство target. Во второй части условия, после оператора AND, мы проверяем, а действительно ли кликнули по тегу li. Если элемент подходит под условие, то на нем сработает та функция (набор действий), которую мы укажем в теле. Теперь при добавлении новых пунктов меню, можно не беспокоиться, что они не будут реагировать на события. Пример кода целиком. let ul = document.querySelector(‘ul’); Делегирование событий в JS, нужно обязательно применять, если на странице есть много элементов с одним и тем же обработчиком события. Всплытие событий в JSКопирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (http://myrusakov.ru)! Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/myrusakov. Если Вы не хотите пропустить новые материалы на сайте, Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы. Порекомендуйте эту статью друзьям: Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте): Комментарии ( 0 ):Для добавления комментариев надо войти в систему. Copyright © 2010-2021 Русаков Михаил Юрьевич. Все права защищены. События и делегирование событий
После того как к элементу «привязали» обработчик событий, вы можете инициировать его через jQuery. Если у события, которое вы хотите инициировать, есть сокращённый метод (см. таблицу выше), вы также можете инициировать это событие, просто вызывая сокращённый метод: Пространство имён событийВнимание! Неудачное решение Однако это отвяжет все обработчики click для всех элементов, чего нам не хочется. Если вы привяжете обработчик событий с помощью пространства имён событий, то можно задать конкретные обработчики событий: Мы также можем использовать пространства имен, чтобы инициировать только определённые события: Привязка нескольких событий за раз Именованная функция как обработчик событийДо сих пор во всех наших примерах в качестве обработчика событий мы передавали анонимную функцию. Тем не менее, вы можете создать функцию заранее и хранить её в переменной, а затем передать эту переменную в качестве обработчика событий. Это полезно, если вы хотите использовать один и тот же обработчик для разных событий или одно событие для различных элементов. Объект событияВсякий раз, когда происходит событие, функция обработчика событий получает один аргумент — объект события, стандартный во всех браузерах. Этот объект имеет много полезных свойств, в том числе следующие: Внутри обработчика событийОтмена действия по умолчаниюЭто позволяет нам использовать «всплывающие» события, которые мы исследуем ниже. Всплывающие событияРассмотрим следующий код: Вы можете увидеть это более наглядно, когда проанализируете такую разметку: Делегирование событийПоведение всплывающих событий позволяет нам делать «делегирование событий» — связывание обработчиков с элементами высокого уровня, а затем определение, какие элементы низкого уровня инициировали событие. Так, мы могли бы связать событие с маркированным списком, а затем выяснить, какой элемент инициировал событие: Конечно, если наш маркированный список содержит пункты, а они в свою очередь содержат другую разметку, при этом мы в действительности заботимся только о нажатом пункте списка, то в суете всё это может оказаться неаккуратным. К счастью, jQuery предоставляет помощника, который позволяет нам определить, о каких элементах нам стоит заботиться, когда идёт привязка к элементу высокого уровня. Делегирование событий имеет два основных преимущества. Во-первых, это позволяет нам привязывать меньше обработчиков событий, чем если бы мы отслеживали щелчки по отдельным элементам; к тому же это может дать большой прирост производительности. Во-вторых, это позволяет нам привязать родительские элементы вроде маркированного списка и наши обработчики событий будут срабатывать как ожидалось, даже если поменяется содержимое. Например, этот код добавляет новый элемент списка после настройки делегирования события; щелчок по новому пункт работает прекрасно, без каких-либо дополнительных событий. РезюмеВ этом разделе мы рассмотрели различные способы взаимодействия пользователя с нашей страницей, в том числе, как мы можем использовать делегирование события более эффективно. В следующем разделе мы перейдём к анимации элементов с помощью методов jQuery. Продвинутая работа с объектом Event на JavaScriptУчебник JavaScriptПрактикаРабота с DOMПрактикаНекоторые продвинутые вещиРекомендованное ES6Некоторые видео могут забегать вперед, тк к этому месту учебника мы прошли еще не весь ES6. Просто пропускайте такие видео, посмотрите потом. РегуляркиРазноеРабота с канвасомПрактикаКонтекстDrag-and-DropПрактика по ООПВаша задача: посмотрите, попробуйте повторить. ПрактикаPromise ES6Библиотека jQueryТк. jQuery устаревает, объявляю эти уроки не обязательными и выношу в конец учебника (так по уровню уроки середины учебника, если что). В перспективе переедет в отдельный учебник по jq. Сейчас мы с вами разберем некоторые продвинутые вещи при работе с объектом Event, а именно: всплытие и перехват, а также делегирование событий. Всплытие событийПредставьте себе, что у вас есть несколько вложенных друг в друга блоков: Когда вы кликаете на самый внутренний блок, событие onclick возникает сначала в нем, а затем срабатывает в его родителе, в родителе его родителя и так далее, пока не дойдет то тега body и далее до тега html (затем до document и до window). И это логично, ведь кликая на внутренний блок, вы одновременно кликаете на все внешние. Давайте убедимся в этом на следующем примере: у нас есть 3 блока, к каждому из них привязано событие onclick: event.targetПусть у нас есть два элемента: div и абзац p, лежащий внутри этого дива. Пусть onlick мы привязали в диву: Когда мы кликаем на этот див, мы можем попасть по абзацу, а можем попасть в место, где этого абзаца нет. Давайте привяжем событие onclick самому верхнему элементу (диву) и будем кликать на разные элементы: на div, на p, на span. С помощью event.target получим самый нижний элемент, в котором случилось событие и выведем его название с помощью tagName. Прекращение всплытияИтак, вы уже знаете, что все события всплывают до самого верха (до тега html, а затем до document, а затем до window). Иногда есть нужда это всплытие остановить. Это может сделать любой элемент, через который всплывает событие. Для этого в коде элемента следует вызвать метод event.stopPropagation(). ПогружениеКроме всплытия событий есть еще и погружение (по научному стадия перехвата). Это значит, что событие сначала идет сверху вниз (стадия перехвата), доходит до нашего элемента (стадия цели) и только потом начинает всплывать (стадия всплытия). Вступление к делегированиюПредставим себе ситуацию: пусть у нас есть ul с несколькими li. К каждой li привязано следующее событие: по нажатию на li ей в конец добавляется ‘!’. Давайте реализуем описанное: Пусть теперь у нас также есть кнопочка, по нажатию на которую в конец ul добавляется новая li с текстом ‘пункт’. Нас ждет сюрприз: привязанное событие не будет работать для новых li! Убедимся в этом: Делегирование событийПри этом работоспособность нашего скрипта должна сохраниться: по-прежнему при клике на li ей в конец будет добавляться ‘!’. Только событие в новом варианте будет навешано на ul: Итак, вот решение нашей задачи через делегирование: Результат выполнения кода: При этом наше решение будет работать автоматически даже для новых li, ведь событие навешено не на li, а на ul: Наш код рабочий, однако не без недостатков. Давайте разберем эти недостатки и напишем более универсальное решение. Универсальное делегирование событийНедостаток нашего кода проявится в том случае, когда внутри li будут какие-то вложенные теги. В нашем случае пусть это будут теги i: Проблема исправляется следующим образом (описанный способ не единственный, но самый простой): с помощью метода closest найдем ближайшую li, котоорая является родителем для event.target вот так: event.target.closest(‘li’). Если же клик был на самой li, то и в event.target, и в event.target.closest(‘li’) будет лежать наша li. Результат выполнения кода: Дополнительные материалыРекомендую посмотреть тренинг по делегированию событий. Что вам делать дальше:Приступайте к решению задач по следующей ссылке: задачи к уроку. Еще раз о делегирование или как правильно использовать свои событияВ этом посте хочу еще раз подчеркнуть механизм работы делегирования и использование собственных событий, чтобы избежать ошибок с которыми мне пришлось столкнутся. Для начала вспомним, что же такое делегирование на следующем примере: В примере есть две ссылки, по клику на которые выполнятся те или иные действия. В данном случае это редактирование и удаление item. Для того, чтоб не добавлять соответствующие слушатели на каждую из ссылок, можно просто добавить один слушатель для контейнера, в котором находятся ссылки и дальше уже по событию получить элемент с которым было связанно это событие. Т.е. в данном примере по клику на одну из ссылок мы получим элемент на который был произведен клик и определим какой из ссылок принадлежит это событие. Кликнув на ссылку ‘Edit’, в консоли выведется сообщение ‘Clicked to edit link’, а для ссылки ‘Delete’ — ‘Clicked to delete link’. Делегирование очень хорошо повышает производительность приложение, так как вы не добавляете слушателя каждому элементу, а только контейнеру. Но мой пост немного не о том, это я решил напомнить, чтоб было понятно описанное ниже. В некоторых случаях, может потребоваться использовать структуры контейнеров с множеством вложений. Для простоты понимания, создадим структуру с одним вложением контейнеров: В проекте, над которым я работаю, мне потребовалось использовать собственные события. Каждый из контейнеров имел свое событие, но оно именовалось одинаково для обоих. Но сам не подозревая того, я применил делегирование к структуре с вложенными контейнерами. Для наглядности, я вынес все в небольшой пример, который мы и рассмотрим. В этом примере мы просто добавляем слушателя родительскому контейнеру и отслеживаем событие ‘some_event’. То же самое делаем и для вложенного контейнера ‘subcontainer’. Если в данный момент вызвать триггер для вложенного контейнера, то в консоли у нас выведется: worked event for container Если честно, когда я добавлял, у себя в проекте слушателей для одинаковых событий, к подобной структуре, то я такого результата не ожидал. События у меня именовались одинаково, да вот тока выполняли совсем разные действия. Когда код такой вот небольшой, то ошибку можно найти сразу, а если это все находится в классах по несколько 100 строк, то очень такие не просто отследить не правильный код. Чтоб избежать ошибки в выполнении, лучше всегда использовать namespaces. Надеюсь этот пост поможет избежать ошибок в коде, с которыми мне пришлось столкнутся, при использовании собственных событий.
|