Throttling vs Debouncing w JavaScript – kiedy i jak je stosować?
W świecie nowoczesnych aplikacji internetowych wydajność i responsywność interfejsu użytkownika są kluczowe. Wiele zdarzeń w przeglądarce, takich jak przewijanie (scroll
), zmiana rozmiaru (resize
) czy ruch myszy (mousemove
), może być wywoływanych dziesiątki lub setki razy na sekundę. Jeśli za każdym razem uruchamiamy kosztowną funkcję, bardzo szybko możemy doprowadzić do problemów z wydajnością. Tu z pomocą przychodzą dwie techniki: throttling i debouncing.
Czym są throttling i debouncing?
Throttling (dławienie)
Throttling ogranicza częstotliwość wywoływania funkcji. Gdy użytkownik wyzwala dane zdarzenie wielokrotnie, throttling sprawia, że funkcja wykonuje się maksymalnie raz na określony przedział czasu, np. co 200 ms – nawet jeśli zdarzenie występuje częściej.
Idealny do sytuacji, w których chcemy aktualizować coś regularnie, ale nie za często.
Przykłady zastosowania:
- Śledzenie pozycji myszy
- Automatyczne zapisywanie danych
- Reagowanie na
scroll
(np. w nieskończonym scrollowaniu)
Debouncing (opóźnianie)
Debouncing działa inaczej – funkcja nie wykonuje się natychmiast, lecz czeka, aż zdarzenia ustaną. Jeśli zdarzenie powtarza się, timer jest resetowany. Funkcja zostanie wykonana tylko po upływie określonego czasu od ostatniego wywołania.
Idealny do sytuacji, w których chcemy wykonać operację dopiero po zakończeniu serii działań użytkownika.
Przykłady zastosowania:
- Wyszukiwanie podczas wpisywania (np. w polu input)
- Walidacja formularzy
- Dynamiczne filtrowanie danych
Porównanie: Throttle vs Debounce
Cecha | Throttling | Debouncing |
---|---|---|
Kiedy się wykonuje | Regularnie, co X ms | Po X ms od ostatniego wywołania |
Częstotliwość wykonania | Maksymalnie raz na X ms | Tylko raz po zakończeniu serii wywołań |
Typowe zastosowania | Scroll, resize, śledzenie myszy | Input search, autosave po edycji |
Przykład z życia | Kontroler klimatyzacji co 5 minut | Alarm, który uruchamia się po bezruchu |
Przykłady kodu
Funkcja throttle
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(function() {
if ((Date.now() - lastRan) >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
Użycie:
const logScroll = () => console.log('Scrolling...');
window.addEventListener('scroll', throttle(logScroll, 300));
Funkcja debounce
function debounce(func, delay) {
let timer;
return function(...args) {
const context = this;
clearTimeout(timer);
timer = setTimeout(() => func.apply(context, args), delay);
};
}
Użycie:
const handleInput = () => console.log('Search triggered');
document.getElementById('search').addEventListener('input', debounce(handleInput, 500));
Gotowe rozwiązania
Jeśli nie chcesz pisać własnych implementacji, możesz skorzystać z biblioteki Lodash, która oferuje _.throttle
i _.debounce
:
import { throttle, debounce } from 'lodash';
const throttled = throttle(() => console.log('Throttle!'), 200);
const debounced = debounce(() => console.log('Debounce!'), 300);
Praktyczne przykłady
Przykład 1: Throttling – ładowanie nowych elementów przy przewijaniu
Scenariusz:
Masz listę produktów, która ładuje więcej elementów, gdy użytkownik zbliża się do końca strony. Nie chcesz, żeby funkcja ładowania była wywoływana dziesiątki razy w ciągu sekundy przy szybkim przewijaniu.
Kod:
<!DOCTYPE html>
<html>
<body style="height: 2000px">
<script>
function throttle(func, limit) {
let lastRun = 0;
return function(...args) {
const now = Date.now();
if (now - lastRun >= limit) {
lastRun = now;
func.apply(this, args);
}
};
}
function loadMoreItems() {
console.log('Ładowanie nowych elementów...');
}
window.addEventListener('scroll', throttle(() => {
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100) {
loadMoreItems();
}
}, 300));
</script>
</body>
</html>
Przykład 2: Debouncing – wyszukiwanie przy wpisywaniu
Scenariusz:
Masz pole wyszukiwania. Nie chcesz wysyłać zapytania do serwera z każdą literą, ale dopiero, gdy użytkownik przestanie pisać na chwilę.
Kod:
<!DOCTYPE html>
<html>
<body>
<input type="text" id="search" placeholder="Wpisz zapytanie..." />
<script>
function debounce(func, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}
function fetchResults(query) {
console.log(`Szukam wyników dla: "${query}"`);
}
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function(e) {
fetchResults(e.target.value);
}, 500));
</script>
</body>
</html>
Przykład 3: Debouncing – autozapis treści po edycji
Scenariusz:
Użytkownik pisze notatkę. Chcesz zapisać treść automatycznie, ale nie po każdej literze – dopiero, gdy przestanie pisać na np. 1 sekundę.
Kod:
<!DOCTYPE html>
<html>
<body>
<textarea id="note" placeholder="Pisz notatkę..." rows="10" cols="40"></textarea>
<script>
function debounce(func, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}
function autosave(content) {
console.log(`Autozapis: ${content}`);
}
const noteArea = document.getElementById('note');
noteArea.addEventListener('input', debounce(function(e) {
autosave(e.target.value);
}, 1000));
</script>
</body>
</html>
Przykład 4: Throttling – śledzenie pozycji myszy
Scenariusz:
Potrzebujesz aktualizować współrzędne myszy, np. do tooltipa lub gry, ale tylko kilka razy na sekundę, by nie przeciążyć renderowania.
Kod:
<!DOCTYPE html>
<html>
<body>
<div id="output">Pozycja: X=0, Y=0</div>
<script>
function throttle(func, limit) {
let lastRun = 0;
return function(...args) {
const now = Date.now();
if (now - lastRun >= limit) {
lastRun = now;
func.apply(this, args);
}
};
}
function updateMousePosition(e) {
document.getElementById('output').textContent = `Pozycja: X=${e.clientX}, Y=${e.clientY}`;
}
document.addEventListener('mousemove', throttle(updateMousePosition, 100));
</script>
</body>
</html>
Podsumowanie przykładów
Sytuacja życiowa | Technika | Korzyść |
---|---|---|
Scrollowanie listy z lazy-loadingiem | Throttle | Rzadziej ładowanie danych, mniejsze zużycie |
Pole wyszukiwania z autouzupełnianiem | Debounce | Mniej zapytań, szybsza odpowiedź serwera |
Autozapis podczas pisania | Debounce | Zapis tylko po zakończeniu pisania |
Śledzenie kursora myszy w interfejsie | Throttle | Płynniejsze animacje, lepsza wydajność |
Kiedy używać której techniki?
Sytuacja | Użyj |
---|---|
Scrollowanie strony i ładowanie danych | Throttle |
Wyszukiwanie po wpisaniu tekstu | Debounce |
Automatyczne zapisywanie danych | Debounce (po edycji) lub Throttle (cyklicznie) |
Reagowanie na resize okna | Throttle |
Walidacja formularza przy wpisywaniu | Debounce |
Podsumowanie
Throttling i debouncing to dwie kluczowe techniki optymalizacji wydajności JavaScript w aplikacjach webowych. Znając ich różnice i zastosowania, możemy tworzyć bardziej responsywne, płynne i przyjazne użytkownikowi interfejsy. Obie techniki są łatwe do wdrożenia, a stosowanie ich w odpowiednich miejscach to znak dojrzałego i świadomego programowania frontendowego.