Что такое Debouncer?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое Debouncer?
Debouncer (или debounce) - это техника оптимизации производительности, которая откладывает выполнение функции до тех пор, пока прекратятся события срабатывания. Debounce гарантирует, что функция будет вызвана только один раз после того, как событие перестанет происходить в течение определённого времени. Это критически важная техника для обработки частых событий типа ввода текста, изменения размера окна или прокрутки страницы.
Проблема, которую решает Debounce
Без debounce частые события могут вызывать функцию много раз в секунду:
// БЕЗ debounce - вызывается при каждом нажатии клавиши
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', (e) => {
// Вызывается сотни раз при быстром вводе
fetch(`/api/search?q=${e.target.value}`)
.then(res => res.json())
.then(data => console.log(data));
});
// Результат: если пользователь вводит "javascript" (10 символов),
// браузер отправит 10+ HTTP запросов. Это плохо для производительности!
Как работает Debounce
Debounce отменяет предыдущий таймер и создаёт новый каждый раз, когда событие срабатывает:
function debounce(func, delay) {
let timeoutId; // Храним ID таймера
return function debounced(...args) {
// Отменяем предыдущий таймер если он ещё не выполнен
clearTimeout(timeoutId);
// Создаём новый таймер
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Использование
const debouncedSearch = debounce((query) => {
console.log('Searching for:', query);
fetch(`/api/search?q=${query}`);
}, 500);
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
Временная шкала Debounce
Представь, что пользователь вводит "hello" со скоростью 1 символ в 100ms:
Время: 0ms 100ms 200ms 300ms 400ms 500ms 600ms
Событие: h e l l o
Вызовы: [таймер отменён] [таймер отменён] ... [таймер отменён] [ВЫПОЛНИТЬ!]
Результат: функция выполнится только один раз в момент 600ms
Реальный пример: Поиск с предложениями
// Debounced функция поиска
function performSearch(query) {
if (!query.trim()) {
clearSuggestions();
return;
}
console.log('Fetching suggestions for:', query);
fetch(`/api/suggestions?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(suggestions => displaySuggestions(suggestions))
.catch(err => console.error('Search error:', err));
}
const debouncedSearch = debounce(performSearch, 300);
// HTML
// <input type="text" id="searchBox" placeholder="Enter search query">
const searchBox = document.getElementById('searchBox');
searchBox.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
function displaySuggestions(suggestions) {
const suggestionList = document.getElementById('suggestions');
suggestionList.innerHTML = suggestions
.map(s => `<div class="suggestion">${s}</div>`)
.join('');
}
function clearSuggestions() {
document.getElementById('suggestions').innerHTML = '';
}
Debounce vs Throttle
Часто debounce путают с throttle. Вот ключевое различие:
Debounce - выполняется один раз ПОСЛЕ прекращения событий:
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
const debouncedFunc = debounce(() => console.log('Debounced!'), 500);
// Событие происходит в моменты: 0, 100, 200, 300 ms
// Выполнение: 800ms (500ms после последнего события в 300ms)
Throttle - выполняется регулярно с фиксированным интервалом:
function throttle(func, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
func.apply(this, args);
lastTime = now;
}
};
}
const throttledFunc = throttle(() => console.log('Throttled!'), 500);
// Событие происходит в моменты: 0, 100, 200, 300 ms
// Выполнение: 0ms, 500ms (контролируется интервалом)
Продвинутый Debounce с контролем
function debounce(func, delay, options = {}) {
let timeoutId;
let lastCallTime = 0;
let lastCallArgs;
let result;
const debounced = function(...args) {
lastCallArgs = args;
const currentTime = Date.now();
// Опция: вызови сразу при первом срабатывании
if (options.leading && currentTime - lastCallTime >= delay) {
result = func.apply(this, args);
lastCallTime = currentTime;
} else {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
result = func.apply(this, lastCallArgs);
lastCallTime = Date.now();
}, delay);
}
return result;
};
// Метод для отмены ожидающего вызова
debounced.cancel = () => {
clearTimeout(timeoutId);
};
// Метод для немедленного выполнения
debounced.flush = () => {
if (timeoutId) {
clearTimeout(timeoutId);
result = func.apply(this, lastCallArgs);
lastCallTime = Date.now();
}
return result;
};
return debounced;
}
// Использование
const debouncedSearch = debounce(
(query) => fetch(`/api/search?q=${query}`),
300,
{ leading: false }
);
// Можно отменить ожидание
debouncedSearch.cancel();
// Можно принудительно выполнить
debouncedSearch.flush();
Практические примеры использования Debounce
1. Сохранение текста в реальном времени
const saveNote = debounce((content) => {
console.log('Saving note:', content);
// POST запрос на сервер
}, 1000);
document.getElementById('noteEditor').addEventListener('input', (e) => {
saveNote(e.target.value);
});
2. Изменение размера окна
const handleResize = debounce(() => {
console.log('Window resized');
// Пересчитай макет
}, 300);
window.addEventListener('resize', handleResize);
3. Поиск при вводе
const searchUsers = debounce((searchTerm) => {
if (searchTerm.length < 2) return;
fetch(`/api/users/search?term=${searchTerm}`)
.then(res => res.json())
.then(users => renderUsers(users));
}, 400);
usernameInput.addEventListener('input', (e) => {
searchUsers(e.target.value);
});
Использование в React
import { useCallback, useRef } from 'react';
function useDebounce(callback, delay) {
const timeoutRef = useRef(null);
return useCallback((...args) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
}, [callback, delay]);
}
function SearchComponent() {
const [query, setQuery] = React.useState('');
const [results, setResults] = React.useState([]);
const debouncedSearch = useDebounce((searchTerm) => {
if (!searchTerm) return;
fetch(`/api/search?q=${searchTerm}`)
.then(res => res.json())
.then(data => setResults(data));
}, 500);
const handleInputChange = (e) => {
const value = e.target.value;
setQuery(value);
debouncedSearch(value);
};
return (
<div>
<input
type="text"
value={query}
onChange={handleInputChange}
placeholder="Search..."
/>
<ul>
{results.map(result => (
<li key={result.id}>{result.name}</li>
))}
</ul>
</div>
);
}
Debounce - это один из самых практичных паттернов оптимизации во фронтенде, который улучшает как производительность приложения, так и пользовательский опыт.