Что такое делегирование DOM Event?

203

Кто-нибудь может объяснить, пожалуйста, делегирование событий в JavaScript и как это полезно?

ксенон
источник
2
Было бы хорошо, если бы была ссылка на более полезный источник информации об этом. 6 часов спустя, это самый популярный в Google рейтинг "dom event Delegation". Может быть, это полезная ссылка? Я не совсем уверен: w3.org/TR/DOM-Level-2-Events/events.html
Шон Макмиллан
Или, может быть, это: sitepoint.com/blogs/2008/07/23/…
Шон Макмиллан,
7
Это популярный. Даже ребята из fb ссылаются на это на своей странице реагирования davidwalsh.name/event-delegate
Sorter
Посмотрите этот javascript.info/event-delegation, это вам очень поможет
Сурадж Джейн

Ответы:

330

Делегирование событий 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, я смотрю на вас).

Вот несколько лучших конкретных примеров кода делегирования событий:

Свежий полумесяц
источник
У меня есть доступ, запрещенный при открытии вашей третьей ссылки Делегирование события без библиотеки javascript и +1 для вашей последней ссылки
bugwheels94
Здравствуйте, спасибо за отличное объяснение. Однако я все еще не совсем понимаю некоторые детали: как я понимаю поток событий дерева DOM (как можно увидеть в 3.1. Распределение событий и поток событий DOM ), объект события распространяется до тех пор, пока не достигнет целевого элемента, а затем вспыхнет. Как получилось, что он может достичь дочерних элементов узла, если родитель этого узла является целью события? Например, как событие может распространяться на, <li>когда оно должно остановиться <ul>? Если мой вопрос все еще неясен или нужна отдельная тема, я был бы рад сделать это.
Aetos
@Aetos: > Почему он может достигать дочерних элементов узла, если родитель этого узла является целью события? Не могу, как я понимаю. Событие завершает фазу 1 (захват) у родителя цели, входит в фазу 2 (цель) на самой цели, затем переходит в фазу 3 (всплывание), начиная с родителя цели. Нигде это не достигает ребенка цели.
Crescent Fresh
@Crescent Хорошо, тогда как событие применимо к дочернему узлу, если оно никогда не достигнет его?
Aetos
1
Действительно отличный ответ. Спасибо за разъяснение делегирования мероприятия соответствующими фактами. Спасибо!
Кшитий Тивари
30

Делегирование событий позволяет избежать добавления прослушивателей событий к конкретным узлам; вместо этого слушатель события добавляется к одному из родителей. Этот прослушиватель событий анализирует всплывающие события, чтобы найти совпадение для дочерних элементов.

Пример 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....
            }
        }
    }

  }
});

http://davidwalsh.name/event-delegate

Усама АбуСитта
источник
1
Рекомендуемая настройка: используйте e.classList.contains () вместо этого в последнем примере: developer.mozilla.org/en-US/docs/Web/API/Element/classList
nc.
7

делегирование событий отличается от определения информатики.

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

Kennebec
источник
6

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

Обычно говорят: использовать делегирование как альтернативу наследования. Наследование - это хорошая стратегия, когда существуют тесные отношения между родительским и дочерним объектами, однако наследование связывает объекты очень тесно. Зачастую делегирование является более гибким способом выражения отношений между классами.

Этот шаблон также известен как «прокси-цепочки». Несколько других шаблонов дизайна используют делегирование - от него зависят состояние, стратегия и шаблоны посетителей.

Эван Тодд
источник
Хорошее объяснение. В примере <ul> с несколькими дочерними элементами <li>, очевидно, <li> являются теми, которые обрабатывают логику нажатия, но это не так, потому что они «делегируют» эту логику отцу <ul>
Хуанма Менендес
6

Концепция делегирования

Если внутри одного родителя много элементов и вы хотите обрабатывать события из них, не привязывайте обработчики к каждому элементу. Вместо этого свяжите единственный обработчик с их родителем и получите дочерний элемент из event.target. Этот сайт предоставляет полезную информацию о том, как реализовать делегирование событий. http://javascript.info/tutorial/event-delegation

Джозеф
источник
6

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

Например, предположим, что вы хотите обработать щелчок по любой ячейке таблицы в большой таблице. Вы могли бы написать цикл для подключения обработчика щелчков к каждой ячейке ... или вы можете подключить обработчик щелчков к таблице и использовать делегирование событий, чтобы запускать его только для ячеек таблицы (а не заголовков таблиц или пробелов внутри грести вокруг клеток и т. д.).

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

Вот простой пример (намеренно многословно, чтобы дать возможность встроенного объяснения): Обработка щелчка по любому 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для проверки того, что если найден соответствующий элемент, он находится внутри контейнера - поскольку, перехватывая событие в контейнере, вы указали, что хотите обрабатывать только элементы в этом контейнере ,

Возвращаясь к нашему примеру таблицы, это означает, что если у вас есть таблица в ячейке таблицы, она не будет соответствовать ячейке таблицы, содержащей таблицу:

TJ Crowder
источник
3

Это в основном то, как делается связь с элементом. .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, с отдельным селектором, он будет подчиняться.

Лукас
источник
2

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

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

2. Второй вариант использования для делегирования событий - это когда мы хотим, чтобы обработчик событий был присоединен к элементу, которого еще нет в DOM, когда наша страница загружена. Это, конечно, потому что мы не можем добавить обработчик событий к тому, чего нет на нашей странице, поэтому в случае устаревания, которое мы кодируем.

Предположим, у вас есть список из 0, 10 или 100 элементов в DOM, когда вы загружаете свою страницу, и еще больше элементов ждут, чтобы добавить их в список. Таким образом, нет никакого способа прикрепить обработчик событий для будущих элементов, или эти элементы еще не добавлены в DOM, а также может быть много элементов, поэтому было бы бесполезно иметь один обработчик событий, прикрепленный к каждому. из них.

Делегация мероприятия

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

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

введите описание изображения здесь

Событие сначала вызывается для кнопки, но затем оно также запускается для всех родительских элементов по одному, поэтому оно также запускает абзац до раздела основного элемента и фактически весь путь вверх в дереве DOM. пока элемент HTML, который является корнем. Итак, мы говорим, что событие всплывает внутри дерева DOM, и именно поэтому оно называется пузырьковым.

1 2 3 4

Целевой элемент: элемент, для которого событие было фактически запущено, называется целевым элементом, поэтому элемент, вызвавший событие, называется целевым элементом. В нашем примере выше, это, конечно, кнопка, на которую нажали. Важной частью является то, что этот целевой элемент хранится в виде свойства в объекте события. Это означает, что все родительские элементы, для которых также будет инициировано событие, будут знать целевой элемент события, то есть, где событие было запущено впервые.

Это приводит нас к делегированию события, потому что, если событие всплывает в дереве 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);
    }
повелитель
источник
1

Делегат в C # похож на указатель на функцию в C или C ++. Использование делегата позволяет программисту инкапсулировать ссылку на метод внутри объекта делегата. Затем объект делегата может быть передан в код, который может вызывать ссылочный метод, без необходимости знать во время компиляции, какой метод будет вызван.

Смотрите эту ссылку -> http://www.akadia.com/services/dotnet_delegates_and_events.html

Bhaskar
источник
5
Я не собираюсь голосовать за это, так как это был, вероятно, правильный ответ на первоначальный вопрос, но сейчас вопрос конкретно о делегировании событий DOM и Javascript
iandotkelly
1

При делегировании событий используются две часто пропускаемые функции событий JavaScript: всплывающее событие и целевой элемент. Когда событие инициируется на элементе, например, щелчком мыши по кнопке, то же событие также запускается на всех предках этого элемента. , Этот процесс известен как всплытие событий; событие всплывает от исходного элемента к вершине дерева DOM.

Представьте себе HTML-таблицу с 10 столбцами и 100 строками, в которой вы хотите, чтобы что-то происходило, когда пользователь щелкает ячейку таблицы. Например, однажды мне нужно было сделать каждую ячейку таблицы такого размера редактируемой при нажатии. Добавление обработчиков событий в каждую из 1000 ячеек было бы серьезной проблемой производительности и, возможно, источником утечек памяти при сбое браузера. Вместо этого, используя делегирование событий, вы добавляете только один обработчик событий к элементу таблицы, перехватываете событие click и определяете, какая ячейка была нажата.

Прити Джа
источник
0
Делегация мероприятия

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

Распространение событий

Когда событие перемещается через 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>

Codepen

antelove
источник