9.1 Delegação de Eventos
Trabalhar com eventos em JavaScript pode exigir uma gestão complexa para otimizar o desempenho e melhorar a arquitetura do aplicativo.
Otimizar o trabalho com eventos em JavaScript é crucial para criar aplicações web de alta performance, especialmente quando se lida com um grande número de elementos. Nesse contexto, a delegação de eventos e a prevenção de handlers desnecessários desempenham um papel fundamental.
Delegação de eventos é um padrão onde o handler do evento é definido no elemento pai, em vez de cada elemento filho individual. Isso permite gerenciar eventos de maneira eficiente para um grande número de elementos filhos, já que o handler é chamado apenas uma vez para o pai, ao invés de para cada elemento.
Exemplo sem delegação de eventos:
<!DOCTYPE html>
<html>
<head>
<title>Exemplo de Delegação de Eventos</title>
</head>
<body>
<ul id="list">
<li>Elemento 1</li>
<li>Elemento 2</li>
<li>Elemento 3</li>
</ul>
<script>
const list = document.querySelector('ul');
const items = document.querySelectorAll('#list li');
items.forEach(item => {
item.addEventListener('click', function(event) {
const li = document.createElement('li');
li.innerText = "Novo elemento, evento não funcionará";
list.appendChild(li);
alert(`Você clicou em: ${event.target.textContent}`);
});
});
</script>
</body>
</html>
Problema: se a lista de elementos aumentar dinamicamente, será necessário adicionar handlers para cada novo elemento.
Solução — exemplo com delegação de eventos:
<!DOCTYPE html>
<html>
<head>
<title>Exemplo de Delegação de Eventos</title>
</head>
<body>
<ul id="list">
<li>Elemento 1</li>
<li>Elemento 2</li>
<li>Elemento 3</li>
</ul>
<script>
const list = document.getElementById('list');
list.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
const li = document.createElement('li');
li.innerText = "Novo elemento, evento funcionará";
list.appendChild(li);
alert(`Você clicou em: ${event.target.textContent}`);
}
});
</script>
</body>
</html>
Vantagens:
- Handler é adicionado apenas ao elemento pai
- Elementos adicionados dinamicamente automaticamente suportarão o handler de eventos
9.2 Prevenção de Handlers Desnecessários
Para evitar a degradação de performance, é importante minimizar a quantidade de handlers de eventos, especialmente se eles estão anexados a muitos elementos ou são frequentemente chamados. Algumas abordagens para otimização:
1. Diminuição da quantidade de handlers: use delegação de eventos para reduzir o número de handlers.
2. Uso de once em addEventListener: se o handler de evento só precisar funcionar uma vez, use a opção { once: true } ao adicionar o handler.
Exemplo:
<!DOCTYPE html>
<html>
<head>
<title>Exemplo de Evento Único</title>
</head>
<body>
<button id="myButton">Clique em mim!</button>
<script>
const button = document.getElementById('myButton');
button.addEventListener('click', function(event) {
alert('Você clicou no botão');
}, { once: true });
</script>
</body>
</html>
3. Debouncing e Throttling: essas técnicas são úteis para otimizar handlers de eventos que são frequentemente chamados, como scroll ou resize.
9.3 Debouncing
Debouncing combina múltiplas chamadas consecutivas de uma função em uma única, executada apenas após a interrupção do fluxo de eventos.
Exemplo:
function debounce(func, wait) {
let timeout;
return function(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
window.addEventListener('resize', debounce(() => {
console.log('Janela redimensionada');
}, 300));
Experimente mudar a largura do widget para ver o resultado
Exemplo de Debouncing
Neste exemplo, a função de busca é chamada apenas depois que o usuário para de digitar por 300 milissegundos.
<!DOCTYPE html>
<html>
<head>
<title>Exemplo de Debouncing</title>
</head>
<body>
<div style="min-height: 55px">
<input type="text" id="searchInput" placeholder="Comece a digitar sua consulta...">
<div id="results"></div>
</div>
<script>
const searchInput = document.getElementById('searchInput');
const results = document.getElementById('results');
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
function performSearch(event) {
const query = event.target.value;
results.textContent = 'Buscando: ' + query;
// Simulação de requisição ao servidor
setTimeout(() => {
results.textContent = 'Você procurou por: ' + query;
}, 500);
}
const debouncedSearch = debounce(performSearch, 300);
searchInput.addEventListener('input', debouncedSearch);
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Exemplo de Debouncing</title>
</head>
<body>
<input type="text" id="searchInput" placeholder="Comece a digitar sua consulta...">
<div id="results"></div>
<script>
const searchInput = document.getElementById('searchInput');
const results = document.getElementById('results');
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
function performSearch(event) {
const query = event.target.value;
results.textContent = 'Buscando por: ' + query;
// Simulação de requisição ao servidor
setTimeout(() => {
results.textContent = 'Você procurou por: ' + query;
}, 500);
}
const debouncedSearch = debounce(performSearch, 300);
searchInput.addEventListener('input', debouncedSearch);
</script>
</body>
</html>
9.4 Throttling
Trolling Throttling garante que a função seja chamada não mais do que uma vez em um determinado intervalo de tempo.
Exemplo:
function throttle(func, limit) {
let inThrottle;
return function(...args) {
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
window.addEventListener('scroll', throttle(() => {
console.log('Janela rolada');
}, 300));
Outro exemplo de Throttling
Neste exemplo, o handler de rolagem é chamado no máximo a cada 200 milissegundos, o que ajuda a evitar sobrecarregar o navegador com eventos de rolagem frequentes.
<!DOCTYPE html>
<html>
<head>
<title>Exemplo de Throttling</title>
</head>
<body>
<div id="scrollContainer" style="height: 200px; overflow-y: scroll;">
<div style="height: 1000px;"></div>
</div>
<div id="status">Comece a rolar para ver o efeito</div>
<script>
const scrollContainer = document.getElementById('scrollContainer');
const status = document.getElementById('status');
function throttle(func, limit) {
let lastFunc;
let lastRan;
return function(...args) {
const context = this;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if ((Date.now() - lastRan) >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
function handleScroll(event) {
status.textContent = 'Rolando... ' + new Date().toLocaleTimeString();
}
const throttledScroll = throttle(handleScroll, 200);
scrollContainer.addEventListener('scroll', throttledScroll);
</script>
</body>
</html>
9.5 Otimização de Eventos com Listeners Passivos
Listeners passivos de eventos (passive event listeners) são usados para melhorar o desempenho, especialmente quando processando eventos de rolagem (scroll). Quando um handler de evento é definido como passivo, isso significa que ele não chamará preventDefault(), permitindo que o navegador otimize o desempenho.
Exemplo:
window.addEventListener('scroll', function(event) {
console.log('Rolando');
}, { passive: true });
GO TO FULL VERSION