O que é delegação de eventos do DOM?


202

Alguém pode explicar a delegação de eventos em JavaScript e como é útil?


2
Seria bom se houvesse um link para uma fonte útil de informações sobre isso. Após 6 horas, esse é o hit principal do Google para "delegação de eventos dom". Talvez este seja um link útil? Eu não sou inteiramente certo: w3.org/TR/DOM-Level-2-Events/events.html
Sean McMillan


7
Este é popular. Mesmo caras fb conectar-se a esta por sua página reactjs davidwalsh.name/event-delegate
Sorter

Veja este javascript.info/event-delegation ele vai ajudá-lo muito
Suraj Jain

Respostas:


330

A delegação de eventos DOM é um mecanismo de resposta a eventos de interface do usuário por meio de um único pai comum em vez de cada filho, através da mágica do evento "bubbling" (também conhecido como propagação de eventos).

Quando um evento é disparado em um elemento, ocorre o seguinte :

O evento é despachado para seu destino EventTargete qualquer ouvinte de evento encontrado lá é acionado. Os eventos de bolha disparam, em seguida, qualquer ouvinte de evento adicional encontrado, seguindo a EventTargetcadeia pai do pai para cima , verificando se há ouvintes de evento registrados em cada EventTarget sucessivo. Essa propagação ascendente continuará até e incluindo o Document.

A bolha de eventos fornece a base para a delegação de eventos nos navegadores. Agora você pode vincular um manipulador de eventos a um elemento pai único, e esse manipulador será executado sempre que o evento ocorrer em qualquer um dos nós filhos (e qualquer um dos filhos por vez). Esta é a delegação de eventos. Aqui está um exemplo disso na prática:

<ul onclick="alert(event.type + '!')">
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
</ul>

Com esse exemplo, se você clicar em qualquer um dos <li>nós filhos , verá um alerta "click!", mesmo que não haja um manipulador de cliques vinculado ao que <li>você clicou. Se nos ligássemos onclick="..."a cada um, <li>você obteria o mesmo efeito.

Então, qual é o benefício?

Imagine que agora você precisa adicionar dinamicamente novos <li>itens à lista acima através da manipulação do DOM:

var newLi = document.createElement('li');
newLi.innerHTML = 'Four';
myUL.appendChild(newLi);

Sem usar a delegação de eventos, você teria que "religar" o "onclick"manipulador de eventos ao novo <li>elemento, para que ele atue da mesma maneira que seus irmãos. Com a delegação de eventos, você não precisa fazer nada. Basta adicionar o novo <li>à lista e pronto.

Isso é absolutamente fantástico para aplicativos da Web com manipuladores de eventos vinculados a muitos elementos, onde novos elementos são criados e / ou removidos dinamicamente no DOM. Com a delegação de eventos, o número de associações de eventos pode ser reduzido drasticamente, movendo-os para um elemento pai comum, e o código que cria dinamicamente novos elementos dinamicamente pode ser dissociado da lógica de vincular seus manipuladores de eventos.

Outro benefício para a delegação de eventos é que o espaço total de memória usado pelos ouvintes de eventos diminui (já que o número de ligações de eventos diminui). Pode não fazer muita diferença para páginas pequenas que são descarregadas com frequência (ou seja, o usuário navega para páginas diferentes com frequência). Mas para aplicativos de longa duração, isso pode ser significativo. Existem situações realmente difíceis de rastrear quando os elementos removidos do DOM ainda reivindicam memória (ou seja, vazam) e, frequentemente, essa memória vazada é vinculada a uma ligação de evento. Com a delegação de eventos, você é livre para destruir elementos filhos sem o risco de esquecer de "desvincular" seus ouvintes de eventos (já que o ouvinte está no ancestral). Esses tipos de vazamentos de memória podem ser contidos (se não forem eliminados, o que às vezes é difícil de fazer. IE, estou olhando para você).

Aqui estão alguns exemplos de código concretos melhores para delegação de eventos:


Eu tenho acesso proibido na abertura de seu terceiro delegação ligação Evento sem uma biblioteca javascript e +1 para o seu último elo
bugwheels94

Olá, obrigado por uma ótima explicação. Ainda estou confuso sobre um certo detalhe: A maneira como entendo o fluxo de eventos da árvore DOM (como pode ser visto em 3.1. Despacho de eventos e fluxo de eventos DOM ) o objeto de evento se propaga até atingir o elemento de destino e borbulha. Como é possível alcançar elementos filho de um nó se o pai desse nó é o destino do evento em questão? por exemplo, como o evento pode se propagar para <li>quando deveria parar <ul>? Se minha pergunta ainda não estiver clara ou precisar de um tópico separado, ficarei feliz em agradecer.
Aetos

@Aetos: > Por que ele pode alcançar elementos filho de um nó se o pai desse nó é o destino do evento em questão? Não pode, como eu o entendo. O evento termina a fase 1 (captura) no pai do alvo, entra na fase 2 (alvo) no próprio alvo e entra na fase 3 (borbulhante) começando no pai do alvo. Em nenhum lugar ele alcança um filho do alvo.
Crescent Fresh

@Crescent Fresh bem, então como o evento se aplica ao nó filho se ele nunca o atinge?
Aetos

1
Realmente ótima resposta. Obrigado por explicar a delegação de eventos com fatos relevantes. Obrigado!
Kshitij Tiwari

30

A delegação de eventos permite evitar adicionar ouvintes de eventos a nós específicos; em vez disso, o ouvinte de evento é adicionado a um pai. Esse ouvinte de evento analisa eventos com bolhas para encontrar uma correspondência nos elementos filho.

Exemplo de JavaScript:

Digamos que temos um elemento UL pai com vários elementos filhos:

<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>

Digamos também que algo precisa acontecer quando cada elemento filho é clicado. Você pode adicionar um ouvinte de evento separado a cada elemento LI individual, mas e se os elementos LI forem frequentemente adicionados e removidos da lista? Adicionar e remover ouvintes de eventos seria um pesadelo, especialmente se o código de adição e remoção estiver em lugares diferentes do seu aplicativo. A melhor solução é adicionar um ouvinte de evento ao elemento UL pai. Mas se você adicionar o ouvinte de evento ao pai, como saberá qual elemento foi clicado?

Simples: quando o evento borbulha até o elemento UL, você verifica a propriedade de destino do objeto de evento para obter uma referência ao nó clicado real. Aqui está um snippet JavaScript muito básico que ilustra a delegação de eventos:

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

Comece adicionando um ouvinte de evento de clique ao elemento pai. Quando o ouvinte de evento é acionado, verifique o elemento do evento para garantir que é o tipo de elemento ao qual reagir. Se é um elemento LI, boom: temos o que precisamos! Se não é um elemento que queremos, o evento pode ser ignorado. Este exemplo é bem simples - UL e LI são uma comparação direta. Vamos tentar algo mais difícil. Vamos ter um DIV pai com muitos filhos, mas tudo o que importa é uma tag A com a classe 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
Ajuste sugerido: use e.classList.contains () no último exemplo: developer.mozilla.org/en-US/docs/Web/API/Element/classList
nc.

7

A delegação de eventos domésticos é algo diferente da definição de ciência da computação.

Refere-se à manipulação de eventos de bolhas de vários elementos, como células da tabela, de um objeto pai, como a tabela. Ele pode manter o código mais simples, especialmente ao adicionar ou remover elementos, e economiza um pouco de memória.


6

Delegação é uma técnica em que um objeto expressa determinado comportamento para o exterior, mas na realidade delega a responsabilidade de implementar esse comportamento em um objeto associado. Isso parece muito semelhante ao padrão de proxy, mas serve a um propósito muito diferente. A delegação é um mecanismo de abstração que centraliza o comportamento do objeto (método).

De um modo geral: use a delegação como alternativa à herança. A herança é uma boa estratégia, quando existe um relacionamento próximo entre o objeto pai e o filho, no entanto, a herança acopla objetos muito de perto. Frequentemente, delegação é a maneira mais flexível de expressar um relacionamento entre classes.

Esse padrão também é conhecido como "cadeias de proxy". Vários outros padrões de design usam delegação - os padrões de Estado, Estratégia e Visitante dependem disso.


Boa explicação. No exemplo de <ul> com vários filhos <li>, aparentemente os <li> são aqueles que lidam com a lógica do clique, mas não é assim porque eles "delegam" essa lógica no pai <ul>
Juanma Menendez

6

O conceito de delegação

Se houver muitos elementos dentro de um pai e você quiser manipular eventos neles, não vincule manipuladores a cada elemento. Em vez disso, vincule o manipulador único ao pai e obtenha o filho de event.target. Este site fornece informações úteis sobre como implementar a delegação de eventos. http://javascript.info/tutorial/event-delegation


6

A delegação de eventos está lidando com um evento que borbulha usando um manipulador de eventos em um elemento de contêiner, mas apenas ativando o comportamento do manipulador de eventos se o evento ocorreu em um elemento no contêiner que corresponda a uma determinada condição. Isso pode simplificar a manipulação de eventos em elementos dentro do contêiner.

Por exemplo, suponha que você queira manipular um clique em qualquer célula da tabela em uma tabela grande. Você poderia escrever um loop para conectar um manipulador de cliques a cada célula ... ou pode conectar um manipulador de cliques na tabela e usar a delegação de eventos para acioná-lo apenas para células da tabela (e não para cabeçalhos da tabela ou o espaço em branco dentro de uma célula). linha em torno das células, etc.).

Também é útil quando você adiciona e remove elementos do contêiner, porque não precisa se preocupar em adicionar e remover manipuladores de eventos nesses elementos; basta conectar o evento no contêiner e manipular o evento quando ele borbulhar.

Aqui está um exemplo simples (intencionalmente detalhado para permitir explicações em linha): Manipulando um clique em qualquer tdelemento em uma tabela de contêiner:

// 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>

Antes de entrar em detalhes, vamos nos lembrar de como os eventos DOM funcionam.

Os eventos DOM são despachados do documento para o elemento de destino (a fase de captura ) e, em seguida, passam do elemento de destino de volta ao documento (a fase de bolhas ). Este gráfico na antiga especificação de eventos DOM3 (agora substituído, mas o gráfico ainda é válido) mostra muito bem:

insira a descrição da imagem aqui

Nem todos os eventos surgem, mas a maioria ocorre, inclusive click.

Os comentários no exemplo de código acima descrevem como ele funciona. matchesverifica se um elemento corresponde a um seletor de CSS, mas é claro que você pode verificar se algo corresponde aos seus critérios de outras maneiras, se não quiser usar um seletor de CSS.

Esse código foi escrito para chamar as etapas individuais detalhadamente, mas em navegadores vagamente modernos (e também no IE se você usar um polyfill), você pode usar closeste, em containsvez do loop:

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

Exemplo ao vivo:

closestverifica o elemento em que você o chama para verificar se ele corresponde ao seletor CSS especificado e, se for, retorna o mesmo elemento; caso contrário, verifica o elemento pai para ver se ele corresponde e retorna o pai, se for o caso; caso contrário, ele verifica o pai do pai, etc. Por isso, encontra o elemento "mais próximo" na lista de ancestrais que corresponde ao seletor. Desde que possam ir além do elemento de recipiente, o código acima usa containspara verificar que se um elemento correspondente foi encontrado, é dentro do recipiente - desde ligando o evento no recipiente, você indicou que você só quer elementos punho dentro desse contêiner .

Voltando ao nosso exemplo de tabela, isso significa que, se você tiver uma tabela dentro de uma célula da tabela, ela não corresponderá à célula da tabela que contém a tabela:


3

É basicamente como a associação é feita ao elemento. .clickaplica-se ao DOM atual, enquanto .on(usando delegação) continuará sendo válido para novos elementos adicionados ao DOM após a associação de eventos.

Qual é o melhor para usar, eu diria que depende do caso.

Exemplo:

<ul id="todo">
   <li>Do 1</li>
   <li>Do 2</li>
   <li>Do 3</li>
   <li>Do 4</li>
</ul>

Evento .Click:

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

Evento .em:

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

Observe que eu separei o seletor no .on. Eu vou explicar o porquê.

Suponhamos que, após essa associação, façamos o seguinte:

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

É aí que você notará a diferença.

Se o evento foi associado via .click, a tarefa 5 não obedecerá ao evento de clique e, portanto, não será removida.

Se foi associado via .on, com o seletor separado, ele obedecerá.


2

Para entender a delegação de eventos primeiro, precisamos saber por que e quando realmente precisamos ou queremos a delegação de eventos.

Pode haver muitos casos, mas vamos discutir dois grandes casos de uso para delegação de eventos. 1. O primeiro caso é quando temos um elemento com muitos elementos filhos nos quais estamos interessados. Nesse caso, em vez de adicionar um manipulador de eventos a todos esses elementos filhos, simplesmente o adicionamos ao elemento pai e determinamos em qual elemento filho o evento foi disparado.

2. O segundo caso de uso para delegação de eventos é quando queremos um manipulador de eventos anexado a um elemento que ainda não esteja no DOM quando nossa página é carregada. Isso é claro, porque não podemos adicionar um manipulador de eventos a algo que não está em nossa página, portanto, em um caso de descontinuação que estamos codificando.

Suponha que você tenha uma lista de 0, 10 ou 100 itens no DOM ao carregar sua página e mais itens estejam aguardando em sua mão para serem adicionados à lista. Portanto, não há como anexar um manipulador de eventos para os elementos futuros ou esses elementos ainda não foram adicionados ao DOM e também pode haver muitos itens; portanto, não seria útil ter um manipulador de eventos anexado a cada deles.

Delegação de Eventos

Tudo bem, então, para falar sobre delegação de eventos, o primeiro conceito sobre o qual realmente precisamos falar é a bolha do evento.

Borbulhar evento: borbulhar evento significa que, quando um evento é disparado ou disparado em algum elemento DOM, por exemplo, clicando em nosso botão aqui na imagem abaixo, o mesmo evento exato também é disparado em todos os elementos pai.

insira a descrição da imagem aqui

O evento é acionado primeiro no botão, mas também será acionado em todos os elementos pai, um de cada vez; portanto, também será acionado no parágrafo da seção o elemento principal e, na verdade, em uma árvore DOM até o elemento HTML que é a raiz. Então, dizemos que o evento borbulha dentro da árvore DOM, e é por isso que é chamado de bolha.

1 2 3 4

Elemento de destino: o elemento no qual o evento foi acionado pela primeira vez chamado de elemento de destino; portanto, o elemento que causou o evento é chamado de elemento de destino. No nosso exemplo acima, é claro, foi o botão que foi clicado. A parte importante é que esse elemento de destino seja armazenado como uma propriedade no objeto de evento. Isso significa que todos os elementos-pai nos quais o evento também será acionado conhecerão o elemento de destino do evento, portanto, onde o evento foi disparado pela primeira vez.

Isso nos leva à delegação de eventos porque se o evento surgir na árvore DOM e se soubermos onde o evento foi acionado, podemos simplesmente anexar um manipulador de eventos a um elemento pai e aguardar que o evento borbulhe. faça o que pretendemos fazer com o nosso elemento de destino. Essa técnica é chamada delegação de eventos. Neste exemplo aqui, poderíamos simplesmente adicionar o manipulador de eventos ao elemento principal.

Tudo bem, então, novamente, a delegação de eventos é não configurar o manipulador de eventos no elemento original no qual estamos interessados, mas anexá-lo a um elemento pai e, basicamente, capturar o evento lá porque ele ocorre. Podemos então agir no elemento em que estamos interessados ​​em usar a propriedade do elemento de destino.

Exemplo: Agora vamos supor que temos dois itens da lista em nossa página, depois de adicionar itens nessas listas programaticamente, queremos excluir um ou mais itens deles. Usando a técnica de delegação de eventos, podemos alcançar nosso objetivo facilmente.

<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>

Adicionando itens nessa lista:

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

Excluir itens:

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

Um delegado em C # é semelhante a um ponteiro de função em C ou C ++. O uso de um delegado permite que o programador encapsule uma referência a um método dentro de um objeto de delegado. O objeto delegado pode ser passado para o código que pode chamar o método referenciado, sem precisar saber em tempo de compilação qual método será chamado.

Consulte este link -> http://www.akadia.com/services/dotnet_delegates_and_events.html


5
Eu não vou votar este para baixo, uma vez que foi, provavelmente, uma resposta correta para a pergunta original, mas a questão é agora especificamente sobre DOM evento delegação e Javascript
iandotkelly

1

A delegação de eventos faz uso de dois recursos frequentemente ignorados dos eventos JavaScript: a subida do evento e o elemento de destino. Quando um evento é acionado em um elemento, por exemplo, um clique do mouse em um botão, o mesmo evento também é acionado em todos os ancestrais desse elemento. . Esse processo é conhecido como bubbling de eventos; o evento borbulha do elemento de origem para o topo da árvore do DOM.

Imagine uma tabela HTML com 10 colunas e 100 linhas na qual você deseja que algo aconteça quando o usuário clicar em uma célula da tabela. Por exemplo, uma vez eu tive que tornar cada célula de uma tabela desse tamanho editável quando clicada. Adicionar manipuladores de eventos a cada uma das 1000 células seria um grande problema de desempenho e, potencialmente, uma fonte de vazamentos de memória com falhas no navegador. Em vez disso, usando a delegação de eventos, você adicionaria apenas um manipulador de eventos ao elemento da tabela, interceptaria o evento click e determinaria em qual célula foi clicada.


0
Delegação de Eventos

Anexe um ouvinte de evento a um elemento pai que é acionado quando um evento ocorre em um elemento filho.

Propagação de Eventos

Quando um evento passa pelo DOM do filho para um elemento pai, isso se chama Propagação de Eventos , porque o evento se propaga ou se move pelo DOM.

Neste exemplo, um evento (onclick) de um botão é passado para o parágrafo pai.

$(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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.