🟨 JavaScript
Сложная
🕐 15 мин

Функция Debounce

Цель: создать функцию debounce(fn, timeout), которая принимает функцию fn и задержку timeout, а возвращает новую функцию, откладывающую вызов fn до тех пор, пока не пройдет timeout миллисекунд бездействия.

💡 Подсказка решения

Для решения этой задачи вам понадобятся две ключевые функции JavaScript:

  1. setTimeout(): чтобы запланировать выполнение функции в будущем.
  2. clearTimeout(): чтобы отменить ранее запланированное выполнение, если функция была вызвана снова.

Вам нужно будет хранить идентификатор таймера в переменной, доступной через замыкание.

👀 Решение #1 (классическое)
/**
 * @param {Function} fn - Исходная функция
 * @param {number} timeout - Задержка в мс
 */
function debounce(fn, timeout) {
  // Переменная для хранения ID таймера.
  // Она находится в замыкании, поэтому сохраняет свое значение между вызовами.
  let timerId = null;
 
  // Возвращаем новую функцию-обертку
  return function(...args) {
    // Сохраняем контекст `this` и аргументы, с которыми была вызвана обертка
    const context = this;
 
    // Если таймер уже был запущен, отменяем его.
    // Это и есть ключевая логика debounce.
    clearTimeout(timerId);
 
    // Запускаем новый таймер.
    timerId = setTimeout(() => {
      // Когда таймер сработает, вызываем исходную функцию,
      // передавая ей сохраненный контекст и аргументы.
      fn.apply(context, args);
    }, timeout);
  };
}

Почему именно так:

  • Замыкание: Переменная timerId «живет» между вызовами возвращаемой функции, что позволяет управлять одним и тем же таймером.
  • clearTimeout: Это сердце механизма. Каждый новый вызов сбрасывает предыдущий «план» на выполнение, гарантируя, что только последний вызов в серии инициирует выполнение.
  • apply(context, args): Это позволяет сохранить исходный контекст (this) и передать все аргументы в целевую функцию fn. Без этого this был бы потерян, а аргументы бы не дошли.
👀 Решение #2 (функциональный стиль, без `this`)
const debounce = (fn, timeout) => {
  let timerId = null;
 
  // Возвращаем стрелочную функцию, которая не имеет своего `this`
  return (...args) => {
    clearTimeout(timerId);
    timerId = setTimeout(() => {
      // `this` здесь будет унаследован из внешней области, а не из места вызова.
      // Для простых функций без контекста это работает отлично.
      fn(...args);
    }, timeout);
  };
};

Почему именно так:

  • Современный синтаксис: Используются стрелочные функции, что делает код короче.
  • Простота: Это решение идеально для случаев, когда вам не нужно заботиться о сохранении контекста this (например, при работе с функциями-хелперами, не являющимися методами объектов).
  • Ограничение: Если fn является методом объекта (obj.method()) и использует this, этот вариант не подойдет, так как this будет потерян. В таких случаях нужно использовать Решение #1.

Описание задачи

Напишите функцию debounce, которая принимает два аргумента:

  1. fn — функция, выполнение которой нужно отложить.
  2. timeout — время в миллисекундах, которое должно пройти без вызовов, прежде чем fn будет выполнена.

Функция debounce должна возвращать новую функцию-обертку. При каждом вызове этой обертки внутренний таймер должен сбрасываться. Исходная функция fn должна быть вызвана только один раз — после того, как с момента последнего вызова пройдет timeout мс.

Примеры использования

// Пример 1: Обработка ввода
const logger = (text) => console.log(`Отправляем запрос: ${text}`);
const debouncedLogger = debounce(logger, 500);
 
// Пользователь печатает "привет"
debouncedLogger('п');
debouncedLogger('пр');
debouncedLogger('при');
// ...пауза 500 мс...
// В консоли появится: "Отправляем запрос: при" (только последний вызов)
 
// Пример 2: Клик по кнопке
let clickCount = 0;
const handleClick = () => {
  clickCount++;
  console.log(`Кнопка нажата ${clickCount} раз`);
};
const debouncedClick = debounce(handleClick, 1000);
 
// Пользователь быстро кликает 5 раз
// debouncedClick();
// debouncedClick();
// debouncedClick();
// ...
// Через 1 секунду в консоли появится: "Кнопка нажата 1 раз"

Требования

  • Функция должна называться debounce.
  • Она должна корректно обрабатывать множественные быстрые вызовы, выполняя fn только один раз.
  • Она должна правильно передавать this и аргументы в исходную функцию fn.

🧑‍💻 Это не баг! Это фича!

Редактор кода намеренно скрыт на мобильном.

Поверь, так лучше: я оберегаю тебя от искушения писать код в неидеальных условиях. Маленький экран и виртуальная клавиатура — не лучшие помощники для программиста.

📖 Сейчас: Изучи задачу, продумай решение. Действуй как стратег.

💻 Потом: Сядь за компьютер, открой сайт и реализуй все идеи с комфортом. Действуй как код-джедай!