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

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

7 ответов

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

Когда событие инициируется на элементе, происходит следующее:

Событие отправлено к его цели 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:

Допустим, у нас есть родительский элемент 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

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

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

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

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

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

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

Живой пример:

// Handle the event on the container
document.getElementById("container").addEventListener("click", function(event) {
    var target = event.target.closest("td");
    if (target && this.contains(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>

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

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

// Handle the event on the container
document.getElementById("container").addEventListener("click", function(event) {
    var target = event.target.closest("td");
    if (target && this.contains(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;
}
<!-- The table wrapped around the #container table -->
<table>
    <tbody>
        <tr>
            <td>
                <!-- This cell doesn't get matched, thanks to the `this.contains(target)` check -->
                <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>
            </td>
            <td>
                This is next to the container table
            </td>
        </tr>
    </tbody>
</table>

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

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

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

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

Делегирование событий

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

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

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

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

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

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

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

<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);
    }

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

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

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

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

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

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

Делегат в 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>

Codepen

Другие вопросы по тегам