Throttle과 Debounce

broccoli·2021년 6월 24일
1

javascript

목록 보기
4/7
post-thumbnail

아주 짧은 시간에 엄청나게 많은 횟수 혹은 여러번 발생하는 이벤트가 의도한게 아니라면 제한시켜주는게 성능상 좋다.

이것에 대한 제한 방법으로 ThrottleDebounce가 있다.

두가지는 비슷한듯 하지만 개념이 있다.

1. Debounce

디바운스는 흔히 보통 엘리베이터를 예로 든다. 엘리베이터는 문이 닫히면 올라가거나 내려간다. 하지만 만약 문이 닫히기 전에 사람이 탄다면 다시 문이열리고 엘리베이터는 닫힐 때마다 기다린다.

즉 이벤트가 일정기간동안 여러번 발생했을 때 마지막 (혹은 처음) 으로 발생한 이벤트만 동작하게 한다는 개념이 Debounce이다.

1-1. Debounce를 react로 구현

import { useState, useEffect } from 'react'

export default function useDebounce(func, wait) {
  const [id, setId] = useState(null)
  useEffect(() => {
    return () => {
      clearTimeout(id)
    }
  }, [id])
  return (...args) => {
    if (id) {
      clearTimeout(id)
    }
    setId(
      setTimeout(() => {
        setId(null)
        func(...args)
      }, wait)
    )
  }
}

useDebounce라는 hook을 구현해봤는데

  • 리턴값은 함수
  • setTimeout에 의해 일정시간 후에 전달받은 함수가 실행된다.
  • 이때 setTimeout의 리턴값을 상태값이 넣어두고 만약 일정기간이 끝나기전에 한번 더 호출이 오면 이전 id를 클리어한다.
  • 마지막에 호출된 함수를 기준으로 일정기간이 지나면 마지막으로 호출된 함수가 실행된다.

2. Throttle

Trottle은 일정한 기간마다 이벤트가 여러번 발생했을 때 한번만 이벤트를 호출하는 방식이다.

즉 1초에 100번이 호출되도 한번만 호출하는 식. Debounce가 1초중 마지막 호출된 놈이 있으면 다시 그놈의 1초를 기다렸다가 더이상 호출되는 놈이 없으면 그놈을 호출하는 식이라면 Throttle은 마지막놈과는 관계없이 그냥 일정기간에 한번 호출되는 것이기 때문에 개념이 다르다.

따라서 Throttle은 보통 스크롤같은 이벤트에 많이 활용된다.

2-1. Throttle을 react로 구현

import { useState, useEffect, useRef } from 'react'

export default function useThrottle(func, wait) {
  const [id, setId] = useState(null)
  const [previous, setPrevious] = useState(Date.now())
  const remaining = useRef(wait)
  let now = previous
  let diff = 0
  useEffect(() => {
    return () => {
      clearTimeout(id)
      now = Date.now()
      diff = wait - (now - previous)
      remaining.current = diff < wait && diff > 0 ? diff : 0
    }
  }, [id, previous])
  return (...args) => {
    if (remaining.current <= 0) {
      func(...args)
      setPrevious(Date.now())
    } else {
      setId(
        setTimeout(() => {
          func(...args)
        }, remaining.current)
      )
    }
  }
}
  • debounce와는 다르게 일정시간이 되면 무조건 한번은 실행이 되어야 한다.
  • setTimeout의 시간 파라미터가 변경이 되어야 한다.
  • 그래서 현재시간과 이전시간의 차이를 비교해서 remaining 값을 바인딩해주었다.

3. underscore가 구현한 Throttle과 Debounce

3-1. _debounce

// When a sequence of calls of the returned function ends, the argument
// function is triggered. The end of a sequence is defined by the `wait`
// parameter. If `immediate` is passed, the argument function will be
// triggered at the beginning of the sequence instead of at the end.
  function debounce(func, wait, immediate) {
    var timeout, previous, args, result, context;

    var later = function() {
      var passed = now() - previous;
      if (wait > passed) {
        timeout = setTimeout(later, wait - passed);
      } else {
        timeout = null;
        if (!immediate) result = func.apply(context, args);
        // This check is needed because `func` can recursively invoke `debounced`.
        if (!timeout) args = context = null;
      }
    };

    var debounced = restArguments(function(_args) {
      context = this;
      args = _args;
      previous = now();
      if (!timeout) {
        timeout = setTimeout(later, wait);
        if (immediate) result = func.apply(context, args);
      }
      return result;
    });

    debounced.cancel = function() {
      clearTimeout(timeout);
      timeout = args = context = null;
    };

    return debounced;
  }

3-2. _throttle

 // Returns a function, that, when invoked, will only be triggered at most once
 // during a given window of time. Normally, the throttled function will run
 // as much as it can, without ever going more than once per `wait` duration;
 // but if you'd like to disable the execution on the leading edge, pass
 // `{leading: false}`. To disable execution on the trailing edge, ditto.
  function throttle(func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};

    var later = function() {
      previous = options.leading === false ? 0 : now();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };

    var throttled = function() {
      var _now = now();
      if (!previous && options.leading === false) previous = _now;
      var remaining = wait - (_now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = _now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };

    throttled.cancel = function() {
      clearTimeout(timeout);
      previous = 0;
      timeout = context = args = null;
    };

    return throttled;
  }

참고링크

profile
🌃브로콜리한 개발자🌟

0개의 댓글