Eventos: addEventListener e propagação
1. Fundamentos do addEventListener
O método addEventListener é a forma moderna e recomendada de registrar manipuladores de eventos no DOM. Sua sintaxe básica recebe três parâmetros:
elemento.addEventListener(tipoEvento, callback, options);
O terceiro parâmetro pode ser um booleano (true para captura, false para bubbling) ou um objeto com propriedades como once, passive e capture.
Diferenças cruciais entre abordagens:
// ❌ Atributo HTML (mistura lógica com marcação)
<button onclick="alert('Clicou')">Clique</button>
// ❌ Propriedade inline (sobrescreve handlers anteriores)
botao.onclick = () => console.log('Primeiro');
botao.onclick = () => console.log('Segundo'); // Apenas este executa
// ✅ addEventListener (permite múltiplos listeners)
botao.addEventListener('click', () => console.log('Primeiro'));
botao.addEventListener('click', () => console.log('Segundo')); // Ambos executam
Para remover listeners, é essencial usar funções nomeadas:
function handler() {
console.log('Evento disparado');
}
elemento.addEventListener('click', handler);
elemento.removeEventListener('click', handler); // Funciona
// ❌ Não funciona com arrow functions anônimas
elemento.addEventListener('click', () => console.log('Não consigo remover'));
2. Tipos de Eventos e Callbacks
O objeto event fornece informações essenciais:
document.querySelector('form').addEventListener('submit', (event) => {
console.log(event.type); // "submit"
console.log(event.target); // Elemento que disparou o evento
console.log(event.currentTarget); // Elemento onde o listener está registrado
event.preventDefault(); // Impede comportamento padrão
});
Comportamento do this em diferentes funções:
const elemento = document.querySelector('button');
// Função regular - this referencia o elemento
elemento.addEventListener('click', function() {
console.log(this === elemento); // true
});
// Arrow function - this mantém escopo léxico
elemento.addEventListener('click', () => {
console.log(this === window); // true (ou undefined em strict mode)
});
3. Propagação de Eventos: Bubbling vs Capturing
Eventos no DOM percorrem três fases: captura (do documento até o alvo), alvo (no elemento alvo) e bolha (do alvo de volta ao documento).
<div id="pai">
<div id="filho">
<button id="botao">Clique</button>
</div>
</div>
<script>
document.getElementById('pai').addEventListener('click',
() => console.log('Pai - Captura'), true);
document.getElementById('filho').addEventListener('click',
() => console.log('Filho - Captura'), true);
document.getElementById('botao').addEventListener('click',
() => console.log('Botão - Alvo'));
document.getElementById('botao').addEventListener('click',
() => console.log('Botão - Alvo 2'));
document.getElementById('filho').addEventListener('click',
() => console.log('Filho - Bolha'));
document.getElementById('pai').addEventListener('click',
() => console.log('Pai - Bolha'));
// Ordem de execução ao clicar no botão:
// 1. Pai - Captura
// 2. Filho - Captura
// 3. Botão - Alvo
// 4. Botão - Alvo 2
// 5. Filho - Bolha
// 6. Pai - Bolha
</script>
4. Controlando a Propagação
document.querySelector('.menu').addEventListener('click', (event) => {
// stopPropagation - interrompe propagação para elementos ancestrais
event.stopPropagation();
// stopImmediatePropagation - interrompe propagação E outros listeners no mesmo elemento
// event.stopImmediatePropagation();
});
// Diferença prática entre target e currentTarget
document.querySelector('ul').addEventListener('click', (event) => {
console.log('target:', event.target.tagName); // LI (elemento clicado)
console.log('currentTarget:', event.currentTarget.tagName); // UL (elemento do listener)
});
5. Delegação de Eventos na Prática
A delegação otimiza performance e simplifica o gerenciamento de elementos dinâmicos:
// ❌ Ineficiente: listener para cada item
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', () => console.log('Item clicado'));
});
// ✅ Eficiente: delegação no elemento pai
document.querySelector('.lista').addEventListener('click', (event) => {
const item = event.target.closest('.item');
if (item) {
console.log('Item clicado:', item.dataset.id);
}
});
// Funciona para elementos adicionados dinamicamente
const novaLista = document.createElement('li');
novaLista.className = 'item';
novaLista.dataset.id = '4';
document.querySelector('.lista').appendChild(novaLista);
6. Eventos no Node.js: EventEmitter
No backend, o Node.js implementa o padrão observer através da classe EventEmitter:
const EventEmitter = require('events');
class Pedido extends EventEmitter {}
const pedido = new Pedido();
pedido.on('processar', (dados) => {
console.log('Processando pedido:', dados.id);
});
pedido.once('finalizar', () => {
console.log('Pedido finalizado (executa apenas uma vez)');
});
pedido.emit('processar', { id: 123 });
pedido.emit('finalizar');
pedido.emit('finalizar'); // Não executa (once)
// Removendo listener
function handler() { console.log('Handler'); }
pedido.on('evento', handler);
pedido.removeListener('evento', handler);
Comparação conceitual: Eventos no navegador são baseados no DOM e propagação hierárquica. No Node.js, são puramente baseados em emissão e escuta, sem propagação automática.
7. Eventos no React: Abordagem Declarativa
React utiliza eventos sintéticos que normalizam comportamento entre navegadores:
function Formulario() {
const handleClick = (event) => {
// SyntheticEvent - objeto normalizado pelo React
console.log(event.type); // "click"
event.stopPropagation(); // Funciona, mas com ressalvas
};
const handleChange = (event) => {
console.log(event.target.value);
};
return (
<form onSubmit={(e) => e.preventDefault()}>
<input onChange={handleChange} />
<button onClick={handleClick}>Enviar</button>
</form>
);
}
Pooling de eventos: O objeto event é reutilizado e suas propriedades são anuladas após o callback. Para acesso assíncrono, use event.persist():
function Componente() {
const handleClick = (event) => {
event.persist(); // Preserva o evento para uso assíncrono
setTimeout(() => {
console.log(event.type); // Funciona com persist()
}, 1000);
};
}
Propagação em React: O bubbling natural ocorre, mas stopPropagation() em React não impede propagação no DOM nativo (apenas no sistema de eventos sintéticos):
function Pai() {
return (
<div onClick={() => console.log('Pai')}>
<Filho />
</div>
);
}
function Filho() {
return (
<button onClick={(e) => {
e.stopPropagation(); // Impede apenas no React, não no DOM
console.log('Filho');
}}>
Clique
</button>
);
}
8. Boas Práticas e Armadilhas Comuns
Vazamento de memória: Sempre limpe listeners em componentes desmontados:
// React useEffect cleanup
useEffect(() => {
const handler = () => console.log('Resize');
window.addEventListener('resize', handler);
return () => {
window.removeEventListener('resize', handler); // Cleanup obrigatório
};
}, []);
Performance: Evite listeners excessivos e prefira delegação:
// ❌ 1000 listeners individuais
document.querySelectorAll('tr').forEach(linha => {
linha.addEventListener('click', () => {});
});
// ✅ 1 listener com delegação
document.querySelector('table').addEventListener('click', (e) => {
const linha = e.target.closest('tr');
if (linha) { /* processa */ }
});
preventDefault() vs stopPropagation():
// preventDefault - impede comportamento padrão (não afeta propagação)
document.querySelector('a').addEventListener('click', (e) => {
e.preventDefault(); // Link não navega
// Propagação continua normalmente
});
// stopPropagation - impede propagação (não afeta comportamento padrão)
document.querySelector('form').addEventListener('submit', (e) => {
e.stopPropagation(); // Evento não propaga para pais
// Comportamento padrão (recarregar página) ainda ocorre
});
Referências
- MDN Web Docs: addEventListener — Documentação oficial completa sobre o método addEventListener, incluindo sintaxe, parâmetros e exemplos detalhados
- JavaScript.info: Bubbling and Capturing — Tutorial aprofundado sobre propagação de eventos, com diagramas e exercícios práticos
- Node.js Documentation: Events — Documentação oficial do módulo Events do Node.js, incluindo EventEmitter, métodos on, once e emit
- React Documentation: SyntheticEvent — Referência completa sobre eventos sintéticos no React, incluindo pooling e diferenças para eventos nativos
- W3Schools: Event Propagation — Guia introdutório sobre propagação de eventos, com exemplos visuais de bubbling e capturing