Кто-нибудь может объяснить, пожалуйста, делегирование событий в JavaScript и как это полезно?
Кто-нибудь может объяснить, пожалуйста, делегирование событий в JavaScript и как это полезно?
Ответы:
Делегирование событий DOM - это механизм реагирования на события пользовательского интерфейса через одного общего родителя, а не каждого потомка, посредством магии «пузырящихся» событий (также называемых распространением событий).
Когда событие инициируется на элементе, происходит следующее :
Событие отправляется к своей цели,
EventTarget
и все найденные там слушатели событий срабатывают. Затем всплывающие события будут запускать любые дополнительные прослушиватели событий, найденные путем следованияEventTarget
родительской цепочки вверх , проверяя наличие прослушивателей событий, зарегистрированных в каждом последующем EventTarget. Это восходящее распространение будет продолжаться вплоть доDocument
.
Пузырьки событий обеспечивают основу для делегирования событий в браузерах. Теперь вы можете привязать обработчик событий к одному родительскому элементу, и этот обработчик будет выполняться всякий раз, когда событие происходит на любом из его дочерних узлов (и, в свою очередь, на любом из их дочерних узлов ). Это делегирование событий. Вот пример этого на практике:
<ul onclick="alert(event.type + '!')">
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
В этом примере, если бы вы щелкнули по любому из дочерних <li>
узлов, вы бы увидели предупреждение "click!"
, даже если нет обработчика щелчка, привязанного к тому, на котором <li>
вы щелкнули. Если мы будем привязаны onclick="..."
к каждому, <li>
вы получите тот же эффект.
Так в чем же выгода?
Представьте, что вам теперь нужно динамически добавлять новые <li>
элементы в приведенный выше список с помощью манипуляций с DOM:
var newLi = document.createElement('li');
newLi.innerHTML = 'Four';
myUL.appendChild(newLi);
Без использования делегирования события вам придется «перепривязать» "onclick"
обработчик события к новому <li>
элементу, чтобы он действовал так же, как его родные элементы. С делегированием событий вам не нужно ничего делать. Просто добавьте новый<li>
в список, и все готово.
Это абсолютно фантастично для веб-приложений с обработчиками событий, связанными со многими элементами, где новые элементы динамически создаются и / или удаляются в DOM. С делегированием событий количество привязок событий может быть значительно уменьшено путем перемещения их к общему родительскому элементу, а код, который динамически создает новые элементы на лету, может быть отделен от логики привязки их обработчиков событий.
Другое преимущество делегирования событий заключается в том, что общий объем памяти, используемый слушателями событий, уменьшается (так как количество привязок событий уменьшается). Это может не иметь большого значения для небольших страниц, которые часто выгружаются (т.е. пользователь часто переходит на разные страницы). Но для долгоживущих приложений это может быть значительным. Есть некоторые действительно трудные для отслеживания ситуации, когда элементы, удаленные из DOM, все еще требуют памяти (то есть они просачиваются), и часто эта утечка памяти связана с привязкой события. С делегированием событий вы можете уничтожать дочерние элементы, не рискуя забыть «отсоединить» их слушателей событий (так как слушатель находится на предке). Эти типы утечек памяти могут тогда быть сдержаны (если не устранены, что порой трудно сделать. IE, я смотрю на вас).
Вот несколько лучших конкретных примеров кода делегирования событий:
focus
и blur
события (которые не пузырь)<li>
когда оно должно остановиться <ul>
? Если мой вопрос все еще неясен или нужна отдельная тема, я был бы рад сделать это.
Делегирование событий позволяет избежать добавления прослушивателей событий к конкретным узлам; вместо этого слушатель события добавляется к одному из родителей. Этот прослушиватель событий анализирует всплывающие события, чтобы найти совпадение для дочерних элементов.
Пример JavaScript:
Допустим, у нас есть родительский элемент UL с несколькими дочерними элементами:
<ul id="parent-list">
<li id="post-1">Item 1</li>
<li id="post-2">Item 2</li>
<li id="post-3">Item 3</li>
<li id="post-4">Item 4</li>
<li id="post-5">Item 5</li>
<li id="post-6">Item 6</li>
Скажем также, что что-то должно произойти, когда щелкает каждый дочерний элемент. Вы можете добавить отдельный прослушиватель событий для каждого отдельного элемента LI, но что, если элементы LI часто добавляются и удаляются из списка? Добавление и удаление прослушивателей событий было бы кошмаром, особенно если код добавления и удаления находится в разных местах вашего приложения. Лучшее решение - добавить прослушиватель событий в родительский элемент UL. Но если вы добавите прослушиватель событий к родителю, как вы узнаете, какой элемент был нажат?
Просто: когда событие всплывает до элемента UL, вы проверяете целевое свойство объекта события, чтобы получить ссылку на фактический выбранный узел. Вот очень простой фрагмент JavaScript, который иллюстрирует делегирование событий:
// Get the element, add a click listener...
document.getElementById("parent-list").addEventListener("click", function(e) {
// e.target is the clicked element!
// If it was a list item
if(e.target && e.target.nodeName == "LI") {
// List item found! Output the ID!
console.log("List item ", e.target.id.replace("post-"), " was clicked!");
}
});
Начните с добавления прослушивателя события click к родительскому элементу. Когда срабатывает прослушиватель событий, проверьте элемент события, чтобы убедиться, что это тип элемента, на который нужно реагировать. Если это элемент LI, бум: у нас есть то, что нам нужно! Если это не тот элемент, который нам нужен, событие можно игнорировать. Этот пример довольно прост - UL и LI - прямое сравнение. Давайте попробуем что-то более сложное. Давайте иметь родительский DIV со многими детьми, но все, что нас волнует, это тег A с классом CSS classA:
// Get the parent DIV, add click listener...
document.getElementById("myDiv").addEventListener("click",function(e) {
// e.target was the clicked element
if(e.target && e.target.nodeName == "A") {
// Get the CSS classes
var classes = e.target.className.split(" ");
// Search for the CSS class!
if(classes) {
// For every CSS class the element has...
for(var x = 0; x < classes.length; x++) {
// If it has the CSS class we want...
if(classes[x] == "classA") {
// Bingo!
console.log("Anchor element clicked!");
// Now do something here....
}
}
}
}
});
делегирование событий отличается от определения информатики.
Это относится к обработке всплывающих событий из многих элементов, таких как ячейки таблицы, из родительского объекта, такого как таблица. Это может упростить код, особенно при добавлении или удалении элементов, и сэкономить часть памяти.
Делегация - это метод, при котором объект выражает определенное поведение извне, но в действительности делегирует ответственность за реализацию этого поведения связанному объекту. Поначалу это звучит очень похоже на шаблон прокси, но служит совершенно другой цели. Делегирование - это механизм абстракции, который централизует поведение объекта (метода).
Обычно говорят: использовать делегирование как альтернативу наследования. Наследование - это хорошая стратегия, когда существуют тесные отношения между родительским и дочерним объектами, однако наследование связывает объекты очень тесно. Зачастую делегирование является более гибким способом выражения отношений между классами.
Этот шаблон также известен как «прокси-цепочки». Несколько других шаблонов дизайна используют делегирование - от него зависят состояние, стратегия и шаблоны посетителей.
Если внутри одного родителя много элементов и вы хотите обрабатывать события из них, не привязывайте обработчики к каждому элементу. Вместо этого свяжите единственный обработчик с их родителем и получите дочерний элемент из event.target. Этот сайт предоставляет полезную информацию о том, как реализовать делегирование событий. http://javascript.info/tutorial/event-delegation
Делегирование события обрабатывает событие, которое всплывает используя обработчик события в элементе контейнера, но активирует поведение обработчика события только в том случае, если событие произошло в элементе в контейнере, который соответствует заданному условию. Это может упростить обработку событий для элементов в контейнере.
Например, предположим, что вы хотите обработать щелчок по любой ячейке таблицы в большой таблице. Вы могли бы написать цикл для подключения обработчика щелчков к каждой ячейке ... или вы можете подключить обработчик щелчков к таблице и использовать делегирование событий, чтобы запускать его только для ячеек таблицы (а не заголовков таблиц или пробелов внутри грести вокруг клеток и т. д.).
Это также полезно, когда вы собираетесь добавлять и удалять элементы из контейнера, потому что вам не нужно беспокоиться о добавлении и удалении обработчиков событий для этих элементов; просто подключите событие к контейнеру и обработайте событие, когда оно вспыхнет.
Вот простой пример (намеренно многословно, чтобы дать возможность встроенного объяснения): Обработка щелчка по любому td
элементу в таблице контейнера:
// Handle the event on the container
document.getElementById("container").addEventListener("click", function(event) {
// Find out if the event targeted or bubbled through a `td` en route to this container element
var element = event.target;
var target;
while (element && !target) {
if (element.matches("td")) {
// Found a `td` within the container!
target = element;
} else {
// Not found
if (element === this) {
// We've reached the container, stop
element = null;
} else {
// Go to the next parent in the ancestry
element = element.parentNode;
}
}
}
if (target) {
console.log("You clicked a td: " + target.textContent);
} else {
console.log("That wasn't a td in the container table");
}
});
table {
border-collapse: collapse;
border: 1px solid #ddd;
}
th, td {
padding: 4px;
border: 1px solid #ddd;
font-weight: normal;
}
th.rowheader {
text-align: left;
}
td {
cursor: pointer;
}
<table id="container">
<thead>
<tr>
<th>Language</th>
<th>1</th>
<th>2</th>
<th>3</th>
</tr>
</thead>
<tbody>
<tr>
<th class="rowheader">English</th>
<td>one</td>
<td>two</td>
<td>three</td>
</tr>
<tr>
<th class="rowheader">Español</th>
<td>uno</td>
<td>dos</td>
<td>tres</td>
</tr>
<tr>
<th class="rowheader">Italiano</th>
<td>uno</td>
<td>due</td>
<td>tre</td>
</tr>
</tbody>
</table>
Прежде чем вдаваться в подробности, давайте вспомним, как работают события DOM.
События DOM отправляются из документа к целевому элементу ( фаза захвата ), а затем переносятся пузырьком из целевого элемента обратно в документ ( фаза всплытия ). Этот рисунок в старой спецификации событий DOM3 (теперь заменен, но рисунок все еще действителен) показывает это очень хорошо:
Не все события пузырьков, но большинство из них, в том числе click
.
Комментарии в приведенном выше примере кода описывают, как это работает. matches
проверяет, соответствует ли элемент селектору CSS, но, конечно, вы можете проверить, соответствует ли что-то вашим критериям другим способом, если вы не хотите использовать селектор CSS.
Этот код написан для подробного вызова отдельных шагов, но в неопределенно современных браузерах (а также в IE, если вы используете polyfill) вы можете использовать closest
и contains
вместо цикла:
var target = event.target.closest("td");
console.log("You clicked a td: " + target.textContent);
} else {
console.log("That wasn't a td in the container table");
}
Живой пример:
closest
проверяет элемент, к которому вы вызываете его, чтобы увидеть, соответствует ли он указанному селектору CSS, и, если это так, возвращает тот же элемент; если нет, он проверяет родительский элемент, чтобы увидеть, соответствует ли он, и возвращает родителя, если так; если нет, он проверяет родителя родителя и т. д. Таким образом, он находит «ближайший» элемент в списке предков, который соответствует селектору. Поскольку это может проходить мимо элемента контейнера, приведенный выше код используется contains
для проверки того, что если найден соответствующий элемент, он находится внутри контейнера - поскольку, перехватывая событие в контейнере, вы указали, что хотите обрабатывать только элементы в этом контейнере ,
Возвращаясь к нашему примеру таблицы, это означает, что если у вас есть таблица в ячейке таблицы, она не будет соответствовать ячейке таблицы, содержащей таблицу:
Это в основном то, как делается связь с элементом. .click
применяется к текущему DOM, в то время как .on
(с использованием делегирования) будет оставаться действительным для новых элементов, добавленных в DOM после сопоставления событий.
Что лучше использовать, я бы сказал, это зависит от случая.
Пример:
<ul id="todo">
<li>Do 1</li>
<li>Do 2</li>
<li>Do 3</li>
<li>Do 4</li>
</ul>
.Click Event:
$("li").click(function () {
$(this).remove ();
});
Событие .on:
$("#todo").on("click", "li", function () {
$(this).remove();
});
Обратите внимание, что я выделил селектор в .on. Я объясню почему.
Предположим, что после этой ассоциации сделаем следующее:
$("#todo").append("<li>Do 5</li>");
Вот где вы заметите разницу.
Если событие было связано с помощью .click, задача 5 не будет подчиняться событию click, и поэтому не будет удалена.
Если он был связан через .on, с отдельным селектором, он будет подчиняться.
Чтобы сначала понять делегирование событий, нам нужно знать, почему и когда нам действительно нужно или нужно делегировать события.
Может быть много случаев, но давайте обсудим два больших варианта использования для делегирования события. 1. Первый случай, когда у нас есть элемент с большим количеством интересующих нас дочерних элементов. В этом случае вместо добавления обработчика событий ко всем этим дочерним элементам мы просто добавляем его в родительский элемент и затем определяем на каком дочернем элементе произошло событие.
2. Второй вариант использования для делегирования событий - это когда мы хотим, чтобы обработчик событий был присоединен к элементу, которого еще нет в DOM, когда наша страница загружена. Это, конечно, потому что мы не можем добавить обработчик событий к тому, чего нет на нашей странице, поэтому в случае устаревания, которое мы кодируем.
Предположим, у вас есть список из 0, 10 или 100 элементов в DOM, когда вы загружаете свою страницу, и еще больше элементов ждут, чтобы добавить их в список. Таким образом, нет никакого способа прикрепить обработчик событий для будущих элементов, или эти элементы еще не добавлены в DOM, а также может быть много элементов, поэтому было бы бесполезно иметь один обработчик событий, прикрепленный к каждому. из них.
Делегация мероприятия
Итак, для того, чтобы говорить о делегировании событий, первая концепция, о которой нам действительно нужно поговорить, это пузырьки событий.
Пузырьки событий: Пузырьки событий означают, что когда событие запускается или запускается на каком-либо элементе DOM, например, нажав нашу кнопку здесь на изображении ниже, то точно такое же событие также запускается на всех родительских элементах.
Событие сначала вызывается для кнопки, но затем оно также запускается для всех родительских элементов по одному, поэтому оно также запускает абзац до раздела основного элемента и фактически весь путь вверх в дереве DOM. пока элемент HTML, который является корнем. Итак, мы говорим, что событие всплывает внутри дерева DOM, и именно поэтому оно называется пузырьковым.
Целевой элемент: элемент, для которого событие было фактически запущено, называется целевым элементом, поэтому элемент, вызвавший событие, называется целевым элементом. В нашем примере выше, это, конечно, кнопка, на которую нажали. Важной частью является то, что этот целевой элемент хранится в виде свойства в объекте события. Это означает, что все родительские элементы, для которых также будет инициировано событие, будут знать целевой элемент события, то есть, где событие было запущено впервые.
Это приводит нас к делегированию события, потому что, если событие всплывает в дереве DOM, и если мы знаем, где событие было запущено, то мы можем просто прикрепить обработчик события к родительскому элементу и ждать, пока событие не всплывет, и мы можем затем сделайте все, что мы собирались сделать с нашим целевым элементом. Эта техника называется делегированием событий. В этом примере мы могли бы просто добавить обработчик событий в основной элемент.
Итак, опять же, делегирование событий состоит не в том, чтобы устанавливать обработчик событий в интересующем нас исходном элементе, а в том, чтобы присоединять его к родительскому элементу и, по сути, перехватывать событие там, потому что оно всплывает. Затем мы можем воздействовать на интересующий нас элемент, используя свойство target element.
Пример: теперь давайте предположим, что у нас есть два элемента списка на нашей странице, после программного добавления элементов в этот список мы хотим удалить из них один или несколько элементов. Используя технику делегирования событий, мы можем легко достичь нашей цели.
<div class="body">
<div class="top">
</div>
<div class="bottom">
<div class="other">
<!-- other bottom elements -->
</div>
<div class="container clearfix">
<div class="income">
<h2 class="icome__title">Income</h2>
<div class="income__list">
<!-- list items -->
</div>
</div>
<div class="expenses">
<h2 class="expenses__title">Expenses</h2>
<div class="expenses__list">
<!-- list items -->
</div>
</div>
</div>
</div>
</div>
Добавление элементов в этот список:
const DOMstrings={
type:{
income:'inc',
expense:'exp'
},
incomeContainer:'.income__list',
expenseContainer:'.expenses__list',
container:'.container'
}
var addListItem = function(obj, type){
//create html string with the place holder
var html, element;
if(type===DOMstrings.type.income){
element = DOMstrings.incomeContainer
html = `<div class="item clearfix" id="inc-${obj.id}">
<div class="item__description">${obj.descripiton}</div>
<div class="right clearfix">
<div class="item__value">${obj.value}</div>
<div class="item__delete">
<button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button>
</div>
</div>
</div>`
}else if (type ===DOMstrings.type.expense){
element=DOMstrings.expenseContainer;
html = ` <div class="item clearfix" id="exp-${obj.id}">
<div class="item__description">${obj.descripiton}</div>
<div class="right clearfix">
<div class="item__value">${obj.value}</div>
<div class="item__percentage">21%</div>
<div class="item__delete">
<button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button>
</div>
</div>
</div>`
}
var htmlObject = document.createElement('div');
htmlObject.innerHTML=html;
document.querySelector(element).insertAdjacentElement('beforeend', htmlObject);
}
Удалить элементы:
var ctrlDeleteItem = function(event){
// var itemId = event.target.parentNode.parentNode.parentNode.parentNode.id;
var parent = event.target.parentNode;
var splitId, type, ID;
while(parent.id===""){
parent = parent.parentNode
}
if(parent.id){
splitId = parent.id.split('-');
type = splitId[0];
ID=parseInt(splitId[1]);
}
deleteItem(type, ID);
deleteListItem(parent.id);
}
var deleteItem = function(type, id){
var ids, index;
ids = data.allItems[type].map(function(current){
return current.id;
});
index = ids.indexOf(id);
if(index>-1){
data.allItems[type].splice(index,1);
}
}
var deleteListItem = function(selectorID){
var element = document.getElementById(selectorID);
element.parentNode.removeChild(element);
}
Делегат в C # похож на указатель на функцию в C или C ++. Использование делегата позволяет программисту инкапсулировать ссылку на метод внутри объекта делегата. Затем объект делегата может быть передан в код, который может вызывать ссылочный метод, без необходимости знать во время компиляции, какой метод будет вызван.
Смотрите эту ссылку -> http://www.akadia.com/services/dotnet_delegates_and_events.html
При делегировании событий используются две часто пропускаемые функции событий JavaScript: всплывающее событие и целевой элемент. Когда событие инициируется на элементе, например, щелчком мыши по кнопке, то же событие также запускается на всех предках этого элемента. , Этот процесс известен как всплытие событий; событие всплывает от исходного элемента к вершине дерева DOM.
Представьте себе HTML-таблицу с 10 столбцами и 100 строками, в которой вы хотите, чтобы что-то происходило, когда пользователь щелкает ячейку таблицы. Например, однажды мне нужно было сделать каждую ячейку таблицы такого размера редактируемой при нажатии. Добавление обработчиков событий в каждую из 1000 ячеек было бы серьезной проблемой производительности и, возможно, источником утечек памяти при сбое браузера. Вместо этого, используя делегирование событий, вы добавляете только один обработчик событий к элементу таблицы, перехватываете событие click и определяете, какая ячейка была нажата.
Присоедините слушатель события к родительскому элементу, который срабатывает, когда событие происходит в дочернем элементе.
Распространение событийКогда событие перемещается через DOM от дочернего к родительскому элементу, это называется распространением события , потому что событие распространяется или перемещается через DOM.
В этом примере событие (onclick) от кнопки передается родительскому абзацу.
$(document).ready(function() {
$(".spoiler span").hide();
/* add event onclick on parent (.spoiler) and delegate its event to child (button) */
$(".spoiler").on( "click", "button", function() {
$(".spoiler button").hide();
$(".spoiler span").show();
} );
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<p class="spoiler">
<span>Hello World</span>
<button>Click Me</button>
</p>