Функция Debounce, которая ограничивает частоту срабатывания обратного вызова. Какая реализация лучше и почему?

Техника Debounce позволяет нам «сгруппировать» несколько последовательных вызовов в один – функция debounce () заставляет функцию ждать определенное количество времени перед повторным запуском.

Я написал оба debounce_1 & debounce_2 функции при решении задачи программирования, они дают тот же результат, но я хочу знать, какая реализация лучше и почему, поскольку в настоящее время я изучаю JavaScript, чтобы улучшить качество своего кода.

/* Write a function called 'debounce' that accepts a function and returns a 
   new function that only allows invocation of the given function after 
   'interval' milliseconds have passed since the last time the returned 
   function ran.

   Additional calls to the returned function within the 'interval' time 
   should not be invoked or queued, but the timer should still get reset. */

function debounce_1(callback, interval) {
  let lastTime = -Infinity;
  
  return function(){
    let now = Date.now(), res;
    
    if (now - lastTime > interval) {
      res = callback();
    }
    
    lastTime = now;
    return res;
  }
}

function debounce_2(callback, interval) {
  let timeout = null;
  
  return function(){
    let res;
    
    if (!timeout) { res = callback(); }
    
    clearInterval(timeout);
    timeout = setTimeout(() => timeout = null, interval);
    
    return res;
  }
}

/* TESTING */
function hello() { return 'hello'; }

// UNCOMMENT BELOW TO TRY EITHER ONE:
const sayHello = debounce_1(hello, 3000);
// const sayHello = debounce_2(hello, 3000);

console.log( sayHello() );                                 // -> 'hello'
setTimeout(function() { console.log(sayHello()); }, 2000); // -> undefined
setTimeout(function() { console.log(sayHello()); }, 4000); // -> undefined
setTimeout(function() { console.log(sayHello()); }, 8000); // -> 'hello'

2 ответа
2

Это не совсем дребезжит. Это похоже, но это не одно и то же, по крайней мере, не так debounce обычно понимается в сообществе программистов. При устранении неполадок, когда событие запускается, функция для запуска позже может быть задерживается (если событие было запущено недавно), но никогда пропущено полностью. Видеть Вот для анимированного примера того, как это выглядит.

Чтобы реализовать дебаунсер, я бы ожидал, что функция для запуска чтобы иметь все необходимые побочные эффекты (например, для изменения текста на экране). Я бы не ожидал, что функция что-нибудь вернет. В очень необычном случае, когда было бы вернуть что-то, я бы ожидал, что он вернет обещание, которое разрешится после следующего запуска указанной функции. Например, со временем дребезга 3 секунды я ожидал бы следующее:

sayHello();
setTimeout(sayHello, 2000);
setTimeout(sayHello, 4000);
setTimeout(sayHello, 8000);

чтобы исходная функция приветствия выполнялась с 7000 миллисекундами и 11000 миллисекундами (поскольку в обеих этих точках последний вызов был на 3 секунды раньше).

Это просто терминология, но в программировании важно общение. (Я бы возложил вину за это на писатели вызова – вы ведь просто реализуете задачу)

Тайм-менеджмент Я полностью согласен с другим ответом, что вычисление разницы в отметках времени друг от друга и от interval математически кажется намного хуже, чем просто использовать setTimeout. Логику легче понять с первого взгляда, когда вам просто нужно очистить и установить тайм-аут.

lastTime если ты мы для вычисления разницы в отметках времени, поскольку Date.now() всегда будет возвращать большое положительное число, вы можете рассмотреть возможность инициализации lastTime просто до 0, а не до -Infinity.

Интервал против тайм-аута Интервал – это не тайм-аут. Хотя ниже просто так получилось работать, читатель кода не мог ожидать, что он:

clearInterval(timeout);
timeout = setTimeout(() => timeout = null, interval);

Использовать clearTimeout с участием setTimeout чтобы прояснить ситуацию.

Это не функциональное программирование – в функциональном программировании используются, среди прочего, чистые функции (которые не зависят от состояния без аргументов). Для отслеживания времени последнего вызова функции или последнего тайм-аута требуется состояние. Логика, которую это реализует, по сути своей является неизменной и нечистой.

    Я думаю, что решение с переменной тайм-аута намного яснее и позволяет избежать арифметических операций. Однако ваша реализация ведет себя странно. То есть использование debounce с высокой частотой отложит обратный вызов на неопределенный срок.

    const debounce = (func, interval) => {
        let timeout = null;
        return ()=>{
            if (timeout) return undefined;
            timeout = setTimeout( ()=>timeout=null, interval)
            return func();
        }
    }
    

    В качестве альтернативы я предлагаю это. Используя этот код, тайм-аут устанавливается только при фактическом вызове функции.

    • Добавление ...args к возвращаемой функции, а также вызов func(...args) делает это более удобным для использования.

      – ггорлен

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *