W3docs

Delegação de Eventos em JavaScript

Aprenda delegação de eventos em JavaScript: como o bubbling permite que um listener pai trate muitos filhos, o padrão event.target + closest() e data-*.

Dominando a Delegação de Eventos em JavaScript

A delegação de eventos é uma técnica poderosa em JavaScript para tratar eventos de forma eficiente, especialmente quando se lida com vários elementos semelhantes ou elementos adicionados dinamicamente. Este guia explica o que é a delegação de eventos, por que ela é útil, como ela se baseia no bubbling de eventos e os padrões e armadilhas que você precisa conhecer para usá-la de forma confiável.

Esta página aborda:

  • O que é delegação de eventos e o comportamento de bubbling do qual ela depende
  • Por que ela economiza memória e trata elementos adicionados dinamicamente
  • O padrão robusto event.target + closest() (e por que tagName sozinho é frágil)
  • Leitura de dados de elementos clicados com atributos data-*
  • Armadilhas comuns, incluindo eventos que não fazem bubbling
  • Quando não usar delegação

Entendendo a Delegação de Eventos

A delegação de eventos aproveita o fato de que a maioria dos eventos faz bubbling pelo DOM: quando um evento é disparado em um elemento, ele então é disparado no pai desse elemento, depois no avô, e assim por diante até o document. Em vez de adicionar um listener de evento a cada elemento individual, você adiciona um único listener a um ancestral comum. Esse único listener trata todos os eventos que fazem bubbling a partir de qualquer descendente.

Se o bubbling é novidade para você, leia Bubbling e capturing primeiro — é o mecanismo sobre o qual toda a técnica é construída.

Benefícios da Delegação de Eventos

  1. Eficiência de Memória: Reduz o número de listeners de eventos em sua aplicação, o que pode economizar memória e melhorar o desempenho, especialmente com um grande número de elementos.
  2. Elementos Dinâmicos: Trata eventos em elementos adicionados dinamicamente ao DOM após o carregamento inicial da página.
  3. Simplicidade: Simplifica o gerenciamento de listeners de eventos, especialmente quando muitos elementos se comportam de forma semelhante.

Como Funciona

A delegação de eventos aproveita a fase de bubbling. Um evento disparado em um filho faz bubbling até seus ancestrais, onde um único listener o captura. Duas propriedades são fundamentais dentro do handler:

  • event.target — o elemento real com o qual o usuário interagiu (o elemento mais profundo). É o que você inspeciona para decidir qual item foi clicado.
  • event.currentTarget — o elemento ao qual o listener está conectado (o pai). Dentro de uma função regular, é o mesmo que this.

O handler lê event.target, determina se ele pertence a um elemento filho relevante e age de acordo.

Exemplos Práticos de Delegação de Eventos

Aqui estão alguns exemplos práticos que mostram como implementar a delegação de eventos em cenários do mundo real:

Exemplo 1: Tratando Cliques em uma Lista

Imagine que você tem uma lista de itens e deseja tratar cliques em cada item sem adicionar um listener de evento a cada item da lista individualmente.

<ul id="myList">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <!-- More items can be added dynamically -->
</ul>

<script>
  document.getElementById('myList').addEventListener('click', function(event) {
    if (event.target.tagName === 'LI') {
      alert('You clicked on ' + event.target.textContent);
    }
  });
</script>

Explicação:

  • O listener de evento é adicionado ao elemento <ul>.
  • Quando um item da lista (<li>) é clicado, o evento faz bubbling até o <ul>, e o listener de evento é acionado.
  • A propriedade event.target é verificada para garantir que o clique veio de um item da lista.

Exemplo 2: Gerenciando Cliques em Botões em uma Interface Dinâmica

Em uma interface com botões adicionados dinamicamente, a delegação de eventos pode ser usada para gerenciar cliques em botões de forma eficaz.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Dynamic Button Feedback Example</title>
    <style>
        #feedback {
            color: blue;
            margin-top: 10px;
        }
    </style>
</head>
<body>
<div id="buttonContainer">
    <!-- Buttons can be added or removed dynamically -->
    <button>Action 1</button>
    <button>Action 2</button>
</div>
<div id="feedback"></div>

<script>
    const buttonContainer = document.getElementById('buttonContainer');
    const feedback = document.getElementById('feedback');

    buttonContainer.addEventListener('click', function(event) {
        if (event.target.tagName === 'BUTTON') {
            feedback.textContent = 'Button clicked: ' + event.target.textContent;
        }
    });
</script>
</body>
</html>

Explicação:

  • Um único listener de evento click é adicionado a um elemento contêiner.
  • Ele verifica se o elemento clicado é um botão e responde ao evento de clique com base em qual botão foi clicado.

Um Padrão Mais Robusto: Use closest()

Verificar event.target.tagName só funciona quando o usuário clica exatamente no elemento esperado. Mas botões e itens de lista frequentemente contêm marcação aninhada — um ícone, um <span>, um <strong>. Se o usuário clicar nesse elemento interno, event.target será o <span>, não o <button>, e uma verificação tagName === 'BUTTON' falhará silenciosamente.

Element.closest(selector) resolve isso. Ele percorre a árvore a partir de event.target e retorna o ancestral mais próximo (incluindo o próprio elemento) que corresponde a um seletor CSS, ou null se nenhum corresponder. Isso torna a delegação resistente a conteúdo aninhado.

<ul id="menu">
  <li class="menu-item"><span>Profile</span></li>
  <li class="menu-item"><span>Settings</span></li>
  <li class="menu-item"><span>Logout</span></li>
</ul>

<script>
  document.getElementById('menu').addEventListener('click', function (event) {
    // Find the .menu-item ancestor, even if a <span> was clicked.
    const item = event.target.closest('.menu-item');

    // closest() can return null (e.g. a click on padding around the items),
    // and we should ignore clicks outside this list entirely.
    if (!item || !this.contains(item)) return;

    console.log('Selected:', item.textContent.trim());
  });
</script>

O guarda !item || !this.contains(item) é importante: closest() continua subindo além do contêiner, portanto, sem this.contains(item) você poderia corresponder a um elemento fora da lista. this aqui é o <ul> (o currentTarget).

Lendo Dados com Atributos data-*

Assim que você sabe qual elemento foi clicado, geralmente precisa de dados sobre ele — um id, um nome de ação, um índice de linha. Codificar lógica por elemento derrota o propósito da delegação. Em vez disso, armazene os dados em cada elemento com um atributo data-* e leia-os a partir do dataset.

<div id="toolbar">
  <button data-action="save">Save</button>
  <button data-action="delete">Delete</button>
  <button data-action="share">Share</button>
</div>

<script>
  const actions = {
    save:   () => console.log('Saving...'),
    delete: () => console.log('Deleting...'),
    share:  () => console.log('Sharing...'),
  };

  document.getElementById('toolbar').addEventListener('click', function (event) {
    const button = event.target.closest('button[data-action]');
    if (!button) return;

    const handler = actions[button.dataset.action];
    if (handler) handler();
  });
</script>

Esse padrão de "mapa de ações" escala de forma limpa: adicione um novo botão com um data-action e uma entrada correspondente no objeto actions — sem novos listeners, sem cadeia de if/else.

Armadilhas e Cuidados

A delegação é poderosa, mas tem pontos de atenção. Tenha esses em mente:

  • Nem todo evento faz bubbling. focus, blur, mouseenter e mouseleave não fazem bubbling, portanto a delegação não os captura em um pai. Use suas contrapartes que fazem bubbling: focusin/focusout para foco e mouseover/mouseout para hover (então filtre com event.target).
  • event.target vs event.currentTarget. target é onde o evento se originou; currentTarget (e this em uma função normal) é o elemento ao qual o listener está conectado. Confundi-los é o bug de delegação mais comum.
  • Arrow functions e this. Uma arrow function não vincula seu próprio this, então this não será o contêiner. Use event.currentTarget em vez disso se você escrever o handler como uma arrow function.
  • Propagação interrompida. Se um handler filho chamar event.stopPropagation(), o evento nunca alcança seu listener delegado. Evite stopPropagation() a menos que você realmente precise dele.
  • Contêineres muito abrangentes. Adicionar o listener ao document para tudo significa que cada clique executa seu handler. Limite o listener ao menor ancestral sensato.

Quando Usar (e Quando Não Usar)

SituaçãoDelegação?
Muitos filhos semelhantes (lista, tabela, grade)Sim — um listener para todos
Filhos adicionados/removidos dinamicamenteSim — sem necessidade de (re)vincular
Um único elemento únicoNão — vincule diretamente; é mais simples
Eventos sem bubbling (focus, blur)Não — use focusin/focusout ou vincule diretamente
Você precisa chamar preventDefault() cedoGeralmente adequado, mas vincule diretamente para ações padrão que você precisa controlar com precisão

Para eventos que você mesmo cria em vez dos eventos embutidos do navegador, consulte Disparando eventos personalizados.

Conclusão

A delegação de eventos é uma técnica essencial para o tratamento eficiente de eventos em JavaScript, particularmente útil em aplicações com inúmeros elementos ou conteúdo dinâmico. Ao entender e utilizar a delegação de eventos, os desenvolvedores podem melhorar significativamente o desempenho e a manutenibilidade de suas aplicações, tornando-as mais responsivas e mais fáceis de gerenciar.

Prática

Prática
Quais são os tipos de eventos JavaScript mencionados no site w3docs?
Quais são os tipos de eventos JavaScript mencionados no site w3docs?
Prática
Por que event.target.closest('.menu-item') é mais confiável do que verificar event.target.tagName para delegação?
Por que event.target.closest('.menu-item') é mais confiável do que verificar event.target.tagName para delegação?
Prática
Quais eventos NÃO fazem bubbling, ou seja, a delegação simples em um pai não os capturará?
Quais eventos NÃO fazem bubbling, ou seja, a delegação simples em um pai não os capturará?
Was this page helpful?