Skip to main content
Практика завдань

Дебаунс та тротл у JavaScript

Що таке Debounce та Throttle?

Debounce та Throttle — це техніки оптимізації продуктивності, які контролюють частоту виконання функцій, особливо при обробці подій, які можуть викликатися дуже часто (прокрутка, зміна розміру, введення тощо).


Debounce

Debounce затримує виконання функції до тих пір, поки не пройде певний проміжок часу з моменту останнього виклику.

Коли використовувати?

  • Пошук з автозаповненням (надсилати запит після того, як користувач перестане вводити)
  • Валідація форм в реальному часі
  • Збереження чернеток (автозбереження після паузи в редагуванні)

Приклад реалізації

javascript
function debounce(func, delay) { let timeoutId; return function(...args) { // Очищення попереднього таймера clearTimeout(timeoutId); // Встановлення нового таймера timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } // Використання const searchInput = document.querySelector('#search'); const handleSearch = debounce((event) => { console.log('Search:', event.target.value); // API запит }, 500); searchInput.addEventListener('input', handleSearch);

Як це працює?

  1. При кожному виклику функції попередній таймер очищається
  2. Встановлюється новий таймер
  3. Функція виконується лише якщо з моменту останнього виклику пройшло delay мілісекунд

Результат:

Якщо користувач вводить "javascript", замість 10 запитів (по одному на кожну літеру), буде надіслано лише один запит через 500 мс після завершення введення.


Throttle

Throttle забезпечує виконання функції не частіше ніж один раз за вказаний проміжок часу.

Коли використовувати?

  • Обробка подій прокрутки (безкінечна прокрутка, ліниве завантаження)
  • Обробка зміни розміру вікна
  • Відстеження руху миші
  • Кнопка відправлення (захист від множинних кліків)

Приклад реалізації

javascript
function throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => { inThrottle = false; }, limit); } }; } // Використання const handleScroll = throttle(() => { console.log('Scroll position:', window.scrollY); // Перевірка, чи досягнуто кінця сторінки }, 1000); window.addEventListener('scroll', handleScroll);

Як це працює?

  1. При першому виклику функція виконується негайно
  2. Прапорець inThrottle встановлюється в true
  3. Усі наступні виклики ігноруються, поки не пройде limit мілісекунд
  4. Після закінчення часу функцію можна викликати знову

Результат:

Якщо користувач швидко прокручує сторінку, функція буде викликатися максимум один раз на секунду, замість сотень разів на секунду.


Порівняння Debounce та Throttle

ХарактеристикаDebounceThrottle
ПоведениеВиконується після паузиВиконується з інтервалами
Перший викликЗатримуєтьсяВиконується негайно
Часті викликиУсі скидаютьсяВиконується один раз за N мс
Сфери використанняПошук, автозбереженняПрокрутка, зміна розміру, відстеження

Візуалізація

Уявіть, що подія відбувається 10 разів на секунду:

Без оптимізації:

█ █ █ █ █ █ █ █ █ █ (10 викликів)

З Debounce (500ms):

░ ░ ░ ░ ░ ░ ░ ░ ░ █ (1 виклик після паузи)

З Throttle (500ms):

█ ░ ░ ░ ░ █ ░ ░ ░ ░ (2 виклики з інтервалами)

Розширена реалізація з ведучими та слідуючими

Debounce з параметрами

javascript
function debounce(func, delay, { leading = false, trailing = true } = {}) { let timeoutId; return function(...args) { const isInvokingLater = !timeoutId; clearTimeout(timeoutId); if (leading && isInvokingLater) { func.apply(this, args); } timeoutId = setTimeout(() => { if (trailing) { func.apply(this, args); } timeoutId = null; }, delay); }; }
  • leading: true — виклик на початку
  • trailing: true — виклик в кінці (за замовчуванням)

Throttle з параметрами

javascript
function throttle(func, limit, { leading = true, trailing = true } = {}) { let inThrottle; let lastArgs; return function(...args) { if (!inThrottle) { if (leading) { func.apply(this, args); } inThrottle = true; setTimeout(() => { inThrottle = false; if (trailing && lastArgs) { func.apply(this, lastArgs); lastArgs = null; } }, limit); } else { lastArgs = args; } }; }

Використання бібліотек

У виробництві часто використовуються готові рішення:

Lodash

javascript
import { debounce, throttle } from 'lodash'; const debouncedSearch = debounce(searchFunction, 500); const throttledScroll = throttle(scrollHandler, 1000);

Скасування виконання

javascript
const debouncedFn = debounce(someFunction, 1000); // Скасувати очікуючий виклик debouncedFn.cancel();

Приклад React

javascript
import { useCallback, useRef, useEffect } from 'react'; function useDebounce(callback, delay) { const timeoutRef = useRef(null); useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, []); return useCallback((...args) => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } timeoutRef.current = setTimeout(() => { callback(...args); }, delay); }, [callback, delay]); } // Використання в компоненті function SearchComponent() { const handleSearch = useDebounce((value) => { console.log('Searching:', value); }, 500); return ( <input type="text" onChange={(e) => handleSearch(e.target.value)} /> ); }

Помилки та підводні камені

Забування про контекст this

javascript
// Неправильно function debounce(func, delay) { let timeoutId; return function() { clearTimeout(timeoutId); timeoutId = setTimeout(func, delay); // втрата this }; } // Правильно function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(this, args), delay); // ✅ }; }

Створення нової дебаунс-функції при кожному рендері

javascript
// Неправильно в React function Component() { // Кожен рендер створює нову функцію const handler = debounce(() => {}, 500); return <input onChange={handler} />; } // Правильно function Component() { // Функція створюється один раз const handler = useMemo( () => debounce(() => {}, 500), [] ); return <input onChange={handler} />; }

Висновок

  • Debounce — затримує виконання до паузи в викликах (пошук, автозбереження)
  • Throttle — обмежує частоту виконання (прокрутка, зміна розміру)
  • Обидві техніки критично важливі для продуктивності додатків
  • У виробництві використовуйте готові бібліотеки (lodash, underscore)
  • Не забувайте про контекст (this) та аргументи в реалізації
  • У React використовуйте useCallback або useMemo для збереження функцій між рендерами

На співбесідах:

Часто запитують реалізувати debounce/throttle з нуля та пояснити відмінності. Важливо розуміти не лише реалізацію, а й практичні випадки використання.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань