9.1 Delegazione degli eventi
Lavorare con gli eventi in JavaScript può richiedere una gestione complessa per ottimizzare le prestazioni e migliorare l'architettura dell'applicazione.
Ottimizzare la gestione degli eventi in JavaScript è fondamentale per creare applicazioni web performanti, soprattutto quando si deve lavorare con un gran numero di elementi. In questo contesto, la delegazione degli eventi e la prevenzione di gestori superflui giocano un ruolo chiave.
La delegazione degli eventi è un pattern in cui il gestore degli eventi viene impostato sull'elemento padre, invece che su ogni singolo elemento figlio. Questo permette di gestire efficacemente gli eventi per un ampio numero di elementi figli, poiché il gestore viene chiamato solo una volta per il padre, e non per ogni elemento.
Esempio senza delegazione degli eventi:
<!DOCTYPE html>
<html>
<head>
<title>Esempio di delegazione degli eventi</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 = "Nuovo elemento, l'evento su cui non funzionerà";
list.appendChild(li);
alert(`Hai cliccato su: ${event.target.textContent}`);
});
});
</script>
</body>
</html>
Problema: se l'elenco degli elementi aumenta dinamicamente, sarà necessario aggiungere gestori a ogni nuovo elemento.
Soluzione — esempio con delegazione degli eventi:
<!DOCTYPE html>
<html>
<head>
<title>Esempio di delegazione degli eventi</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 = "Nuovo elemento, l'evento su cui funzionerà";
list.appendChild(li);
alert(`Hai cliccato su: ${event.target.textContent}`);
}
});
</script>
</body>
</html>
Vantaggi:
- Il gestore viene aggiunto solo all'elemento padre
- Gli elementi aggiunti dinamicamente supporteranno automaticamente il gestore degli eventi
9.2 Prevenzione dei gestori superflui
Per evitare un calo delle prestazioni, è importante minimizzare il numero di gestori degli eventi, soprattutto se sono collegati a molti elementi o vengono chiamati spesso. Alcuni approcci per l'ottimizzazione:
1. Riduzione del numero di gestori: utilizza la delegazione degli eventi per ridurre il numero di gestori.
2. Uso di once in addEventListener: se il gestore dell'evento deve essere
eseguito solo una volta, usa l'opzione { once: true }
quando aggiungi il gestore.
Esempio:
<!DOCTYPE html>
<html>
<head>
<title>Esempio di evento singolo</title>
</head>
<body>
<button id="myButton">Premi su di me!</button>
<script>
const button = document.getElementById('myButton');
button.addEventListener('click', function(event) {
alert('Hai premuto il pulsante');
}, { once: true });
</script>
</body>
</html>
3. Debounce e Throttle: queste tecniche sono utili per ottimizzare i gestori degli eventi che vengono chiamati spesso,
come scroll
o resize
.
9.3 Debounce
Il debounce combina diverse chiamate consecutive di una funzione in una sola, da eseguire solo quando il flusso di eventi è terminato.
Esempio:
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('Window resized');
}, 300));
Prova a cambiare la larghezza del widget per vedere il risultato
Esempio di debounce
In questo esempio, la funzione di ricerca viene chiamata solo dopo che l'utente ha smesso di digitare per 300 millisecondi.
<!DOCTYPE html>
<html>
<head>
<title>Esempio di debounce</title>
</head>
<body>
<div style="min-height: 55px">
<input type="text" id="searchInput" placeholder="Inizia a digitare la query...">
<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 = 'Ricerca: ' + query;
// Simulazione della richiesta al server
setTimeout(() => {
results.textContent = 'Hai cercato: ' + query;
}, 500);
}
const debouncedSearch = debounce(performSearch, 300);
searchInput.addEventListener('input', debouncedSearch);
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Esempio di debounce</title>
</head>
<body>
<input type="text" id="searchInput" placeholder="Inizia a digitare la query...">
<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 = 'Searching for: ' + query;
// Simulazione della richiesta al server
setTimeout(() => {
results.textContent = 'Hai cercato: ' + query;
}, 500);
}
const debouncedSearch = debounce(performSearch, 300);
searchInput.addEventListener('input', debouncedSearch);
</script>
</body>
</html>
9.4 Throttle
Throttling garantisce che la funzione venga chiamata non più di una volta in un intervallo di tempo specificato.
Esempio:
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('Window scrolled');
}, 300));
Un altro esempio di throttle
In questo esempio, il gestore dello scroll viene chiamato non più spesso di ogni 200 millisecondi, il che aiuta a prevenire il sovraccarico del browser durante frequenti eventi di scroll.
<!DOCTYPE html>
<html>
<head>
<title>Esempio di throttle</title>
</head>
<body>
<div id="scrollContainer" style="height: 200px; overflow-y: scroll;">
<div style="height: 1000px;"></div>
</div>
<div id="status">Inizia a scorrere per vedere l'effetto</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 = 'Scrolling... ' + new Date().toLocaleTimeString();
}
const throttledScroll = throttle(handleScroll, 200);
scrollContainer.addEventListener('scroll', throttledScroll);
</script>
</body>
</html>
9.5 Ottimizzazione degli eventi con i listener passivi
I listener passivi (passive event listeners) sono utilizzati per migliorare le prestazioni, soprattutto durante
la gestione degli eventi di scroll (scroll
). Quando il gestore dell'evento è impostato come passivo, significa che non
richiamerà preventDefault()
, permettendo al browser di ottimizzare le prestazioni.
Esempio:
window.addEventListener('scroll', function(event) {
console.log('Scrolling');
}, { passive: true });
GO TO FULL VERSION