디바운스와 쓰로틀

Hyeon·2021년 9월 16일
0

왜 사용하는가?

scroll, resize, input, mouseover 등의 이벤트는 짧은 시간에 여러 번 발생하고, 이는 성능상의 문제를 일으킬 수 있다. 디바운스와 쓰로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 과도한 이벤트 호출을 막는다.

예제 코드 세팅

간단한 예제를 통해 작동 방식을 알아보겠다. 일반 클릭, 디바운스 클릭, 쓰로틀 클릭으로 구분하고 클릭에 따라 카운트가 어떻게 달라지는지 살펴보자.

틀이 되는 코드는 다음과 같다.

<!DOCTYPE html>
<html>
  <body>
    <button>click</button>
    <div>
      <pre>일반 클릭: <span class="normal">0</span></pre>
      <pre>디바운스 클릭: <span class="debounce">0</span></pre>
      <pre>스로틀 클릭: <span class="throttle">0</span></pre>
    </div>
    <script>
      const $button = document.querySelector("button");
      const $normal = document.querySelector(".normal");
      const $debounce = document.querySelector(".debounce");
      const $throttle = document.querySelector(".throttle");

      $button.addEventListener("click", () => {
        $normal.textContent = Number($normal.textContent) + 1;
        console.log('normal');
      });
      
    </script>
  </body>
</html>

현재는 일반 클릭의 카운트만 되고 있다.

디바운스

디바운스는 짧은 시간 간격으로 발생하는 이벤트를 그룹화해서 마지막 한 번만 이벤트 핸들러가 호출되도록 한다. 다시 말하면 디바운스는 이벤트가 연속해서 발생하면 이벤트 핸들러를 호출하지 않다가 일정 시간 동안 이벤트가 발생하지 않으면 그때 이벤트 핸들러를 호출한다.

<!DOCTYPE html>
<html>
  <body>
    <button>click</button>
    <div>
      <pre>일반 클릭: <span class="normal">0</span></pre>
      <pre>디바운스 클릭: <span class="debounce">0</span></pre>
      <pre>스로틀 클릭: <span class="throttle">0</span></pre>
    </div>
    <script>
      const $button = document.querySelector("button");
      const $normal = document.querySelector(".normal");
      const $debounce = document.querySelector(".debounce");
      const $throttle = document.querySelector(".throttle");

      const debounce = (callback, delay) => {
        let timerId;

        //timerId를 기억하는 클로져를 반환한다.
        return (e) => {
          //delay가 끝나기전 다시 호출 되었을 경우, 타이머를 취소하고 새로운 타이머를 설정한다.
          //delay가 끝날때까지 호출이 되지 않으면 callback함수를 실행한다.
          if (timerId) clearTimeout(timerId);
          timerId = setTimeout(callback, delay, 'debounce 실행');
        };
      };

      $button.addEventListener("click", () => {
        $normal.textContent = Number($normal.textContent) + 1;
        console.log('normal');
      });

      //debounce 함수가 반환하는 클로져를 이벤트 핸들러로 등록한다.
      $button.addEventListener("click", debounce((message)=>{
        $debounce.textContent = Number($debounce.textContent) + 1;
        console.log(message);
      },500));
      
    </script>
  </body>
</html>

debounce 함수가 반환하는 클로져를 이벤트 핸들러로 등록했다. 이는 클로져의 특성을 활용해서 이벤트가 여러번 호출되었을 때 이전 timerId를 기억하기 위함이다. 이벤트가 호출되면, 이전 timerId의 존재여부를 확인하여 있다면 해당 타이머를 제거하고, 새로운 타이머를 설정한다. 없다면 새로운 타이머만 설정한다. delay로 설정한 시간내에 debounce함수가 호출되지 않으면 callbabk함수가 실행된다. 실행결과는 다음과 같다.

디바운스는 resize 이벤트 처리, input에 따라 ajax요청을 보내는 자동완성, 버튼 중복 클릭 방지 등에 사용될 수 있다.

쓰로틀

쓰로틀링은 짧은 시간 간격으로 이벤트가 발생했을 때 이벤트를 그룹화해서 일정 시간단위로 이벤트 핸들러가 호출되도록 한다. 코드를 보자! 긴 것 같지만 바뀐 부분은 몇 줄 되지 않는다. throttle 함수와 이를 호출하는 부분이 추가되었다.

<!DOCTYPE html>
<html>
  <body>
    <button>click</button>
    <div>
      <pre>일반 클릭: <span class="normal">0</span></pre>
      <pre>디바운스 클릭: <span class="debounce">0</span></pre>
      <pre>스로틀 클릭: <span class="throttle">0</span></pre>
    </div>
    <script>
      const $button = document.querySelector("button");
      const $normal = document.querySelector(".normal");
      const $debounce = document.querySelector(".debounce");
      const $throttle = document.querySelector(".throttle");

      const debounce = (callback, delay) => {
        let timerId;

        //timerId를 기억하는 클로져를 반환한다.
        return (e) => {
          //delay가 끝나기전 다시 호출 되었을 경우, 타이머를 취소하고 새로운 타이머를 설정한다.
          //delay가 끝날때까지 호출이 되지 않으면 callback함수를 실행한다.
          if (timerId) clearTimeout(timerId);
          timerId = setTimeout(callback, delay, 'debounce 실행');
        };
      };


      const throttle = (callback, delay) => {
        let timerId;

        //timerId를 기억하는 클로져를 반환한다.
        return (e) => {
          //delay가 끝나기전 다시 호출 되었을 경우, 아무일도 하지않는다.
          //delay가 지나면 callback함수를 실행하고 timerId를 null로 만들어 새로운 타이머를 설정할 수 있도록 한다.
          if(timerId) return;
          timerId = setTimeout((message)=>{
              callback(message);
              timerId= null;
          }, delay, 'throttle 실행')
        }
      }


      $button.addEventListener("click", () => {
        $normal.textContent = Number($normal.textContent) + 1;
        console.log('normal')
      });

      //debounce 함수가 반환하는 클로져를 이벤트 핸들러로 등록한다.
      $button.addEventListener("click", debounce((message)=>{
        $debounce.textContent = Number($debounce.textContent) + 1;
        console.log(message);
      },500));
      
      //throttle 함수가 반환하는 클로져를 이벤트 핸들러로 등록한다.
      $button.addEventListener("click", throttle((message)=>{
        $throttle.textContent = Number($throttle.textContent) + 1;
        console.log(message);
      },500))
    </script>
  </body>
</html>

디바운스와 방식은 비슷하다. 이전 timerId를 기억하기 위해서 클로져를 반환했다. 클로져에서는 timerId가 존재한다면 clear를 하는 것이 아니라 아무일도 하지 않도록 return 했다. 그리고 나서 setTimeOut의 실행부분에서 delay이후에 콜백을 실행하고, timerId를 비웠다. 이렇게 함으로써 다시 함수가 실행되었을 때 타이머가 새롭게 설정되도록 했다. 이렇게 되면 결국 설정한 delay간격으로 callback이 호출된다. 실행결과는 다음과 같다.

쓰로틀링은 scroll 이벤트 처리나 무한스크롤 등에 사용될 수 있다.

profile
요즘 인터렉티브한 웹에 관심이 많습니다.

0개의 댓글