Що таке делегація DOM Event?


202

Чи може хто-небудь пояснити делегацію подій в JavaScript і чим це корисно?


2
Було б добре, якби було посилання на Smoe корисне джерело інформації про це. 6 годин, це найкращий хіт Google для "делегації події". Можливо, це корисне посилання? Я не зовсім впевнений: w3.org/TR/DOM-Level-2-Events/events.html
Шон Макміллан


7
Це популярне. Навіть FB хлопці посилаються на це їх reactjs сторінки davidwalsh.name/event-delegate
сортувальник

Дивіться це javascript.info/event-delegation, він вам дуже допоможе
Сурай Джайн

Відповіді:


330

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

Коли подія запускається на елемент, відбувається таке :

Подія відправляється до мети, EventTargetі будь-які слухачі подій, які там знайдеться, спрацьовують. Потім події " Bubbling" викликатимуть будь-яких додаткових слухачів подій, знайдених шляхом просування по 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: > Як це може дійти до дочірніх елементів вузла, якщо батько цього вузла є цільовою подією? Не можу, як я це розумію. Подія закінчує фазу 1 (захоплення) у батька цілі, вводить фазу 2 (ціль) на самій цілі, потім вводить фазу 3 (барботування), починаючи з батьківського цілі. Ніде не дістається дитині цілі.
Півмісяць свіжий

@Crescent Fresh добре, то як подія застосовується до дочірнього вузла, якщо вона ніколи не досягає цього?
Аетос

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

Почніть з додавання слухача подій клацання до батьківського елемента. Коли спрацьовує слухач події, перевірте елемент події, щоб переконатися, що тип елемента на нього реагує. Якщо це елемент LI, бум: у нас є те, що нам потрібно! Якщо це не елемент, який ми хочемо, подію можна ігнорувати. Цей приклад досить простий - UL та LI - це прямолінійне порівняння. Спробуємо щось складніше. Давайте матимемо батьківський DIV з багатьма дітьми, але все, що нам цікаво, - це тег із класом 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

Делегування події dom - це щось інше, ніж визначення інформатики.

Він посилається на обробку подій, що перетворюються з багатьох елементів, таких як комірки таблиці, від батьківського об'єкта, як таблиця. Він може зберегти код простішим, особливо при додаванні або видаленні елементів, і економить деяку кількість пам'яті.


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для перевірки того, що якщо знайдений відповідний елемент, він знаходиться в контейнері - оскільки, підключивши подію до контейнера, ви вказали, що хочете обробляти лише елементи в цьому контейнері .

Повернувшись до нашого прикладу таблиці, це означає, що якщо у вас є таблиця в комірці таблиці, вона не буде відповідати клітинці таблиці, що містить таблицю:


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>

.Натисніть подію:

$("li").click(function () {
   $(this).remove ();
});

Подія .on:

$("#todo").on("click", "li", function () {
   $(this).remove();
});

Зауважте, що я розділив селектор у .on. Я поясню, чому.

Припустимо, після цієї асоціації зробимо наступне:

$("#todo").append("<li>Do 5</li>");

Саме тут ви помітите різницю.

Якщо подія була пов’язана через .click, завдання 5 не підкорятиметься події клацання, і тому вона не буде видалена.

Якщо він був пов'язаний через .on, з селектором окремим, він підкоряється.


2

Щоб зрозуміти делегацію подій, спершу ми повинні знати, чому і коли насправді потрібна або хочемо делегація подій.

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

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

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

Делегація подій

Гаразд, тому для того, щоб поговорити про делегування подій, першою концепцією, про яку ми насправді потрібно поговорити, є кипуча подій.

Пузир події: Бублинг подій означає, що коли подія запускається або спрацьовує на якомусь елементі DOM, наприклад, натиснувши на нашу кнопку тут на нижньому зображенні, то точно та сама подія також запускається на всіх батьківських елементах.

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

Подія спочатку запускається на кнопку, але потім вона також буде запускатися по всіх батьківських елементах один за одним, тому вона також запустить на абзац до розділу головний елемент і фактично весь шлях у дерево DOM до елемента HTML, що є коренем. Тому ми кажемо, що подія пухиряється всередині дерева DOM, і тому це називається барботажем.

1 2 3 4

Цільовий елемент: Елемент, на якому насправді подія була запущена, називається цільовим елементом, тому елемент, який спричинив подію, називається цільовим елементом. У нашому наведеному вище прикладі це, звичайно, кнопка, яку натиснули. Важлива частина полягає в тому, що цей цільовий елемент зберігається як властивість в об'єкті події. Це означає, що всі батьківські елементи, на яких також буде запускатися подія, будуть знати цільовий елемент події, тому там, де подія була вперше запущена.

Це приводить нас до делегації подій, тому що якщо подія пухиряється в дереві 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);
    }

1

Делегат у C # схожий на функцію вказівника на C або C ++. Використання делегата дозволяє програмісту інкапсулювати посилання на метод всередині об'єкта делегата. Потім об'єкт делегування може бути переданий до коду, який може викликати посилається метод, не знаючи під час компіляції, який метод буде викликаний.

Дивіться це посилання -> http://www.akadia.com/services/dotnet_delegates_and_events.html


5
Я не збираюся голосувати за це, оскільки це було, мабуть, правильною відповіддю на початкове запитання, але зараз питання стосується конкретно делегації подій DOM & Javascript
iandotkelly

1

Делегування подій використовує дві часто недооцінені функції подій JavaScript: пухирі подій та цільовий елемент. Коли подія запускається на елемент, наприклад, натискання кнопкою миші на кнопку, ця подія також запускається для всіх предків цього елемента . Цей процес відомий як кипуча подія; подія піднімається від початкового елемента до вершини дерева DOM.

Уявіть HTML-таблицю з 10 стовпцями та 100 рядками, у яких ви хочете, щоб щось сталося, коли користувач натискає на комірку таблиці. Наприклад, колись мені довелося зробити кожну клітинку таблиці такого розміру під час натискання. Додавання обробників подій до кожної з 1000 комірок буде головною проблемою продуктивності і, можливо, джерелом витоків пам'яті, що розбивається в браузері. Замість цього, використовуючи делегування подій, ви додали б лише один обробник подій до елемента таблиці, перехоплюєте подію кліку та визначаєте, яку клітинку було натиснуто.


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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.