Javascript/Concept

타이머 - Debounce와 Throttle

min' 2025. 4. 17. 08:31
728x90
반응형

짧은 시간 연속으로 발생하는 이벤트(scroll, resize, input, mousemove 등)는

과도하게 호출되어 성능에 문제를 일으킬 수 있다.

이럴 때 성능 최적화를 위해 사용되는 기술인 Debounce와 Throttle을 사용할 수 있다.

 

Debounce와 Throttle은

짧은 시간 간격으로 연속으로 발생하는 이벤트를 그룹화하여 과도한 이벤트 호출을 방지하는 프로그래밍 기법이다.

이를 구현하기 위해서는 타이머 함수가 사용된다.

 

1. Debounce:

특정 시간동안 이벤트가 발생하지 않을 때, 딱 한 번만 함수를 실행하는 것이다.

이벤트가 연속적으로 발생하면 이전의 실헹 대기 중인 함수 호출을 취소하고,

가장 마지막 이벤트 발생 시점부터 설정된 시간(delay) 이후에 함수를 실행한다.

 

주로 사용하는 곳은,

1) 검색어 자동 완성:

사용자가 검색어를 입력할 때마다 API 호출을 하는 대신,

입력이 멈춘 후 잠시 뒤에 API를 호출하여 불필요한 네트워크 요청을 줄일 수 있다.

 

2) 창 크기 조절 이벤트:

창 크기가 계속 변경되는 동안에는 함수를 실행하지 않고,

크기 조절이 완료된 후 한 번만 함수를 실행하여 성능을 개선할 수 있다.

 

3) 버튼 중복 클릭 방지 처리

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Debounce</title>
  </head>
  <body>
    <input type="text" />
    <div class="msg"></div>

    <script>
      const $input = document.querySelector("input");
      const $msg = document.querySelector(".msg");

      const debounce = (callback, delay) => {
        let timerId;
        return (event) => {
          if (timerId) clearTimeout(timerId);
          // 만약 timerId가 존재하면 이전 타이머를 취소한다.
          timerId = setTimeout(callback, delay, event);
          // delay 시간 후에 callback 함수를 실행하도록 예약한다.
          // 결과적으로, 이벤트가 delay 시간 내에 다시 발생하면 이전 타이머가 취소되고 새로운 타이머가 설정되므로,
          // callback 함수는 마지막 이벤트 발생 후 delay 시간 이후에 딱 한 번만 실행된다.
        };
      };

      $input.oninput = debounce((e) => {
        $msg.textContent = e.target.value;
      }, 300);
    </script>
  </body>
</html>

 

2. Throttle:

스로틀은 설정된 시간 간격(delay) 동안 함수가 최대 한 번만 실행되도록 제한하는 기법이다.

이벤트가 연속적으로 발생하더라도, 지정된 시간 간격이 지나야만 함수를 다시 실행할 수 있다.

 

주로 사용하는 곳은,

1) 스크롤 이벤트:

스크롤 이벤트가 너무 자주 발생하면 성능에 영향을 줄 수 있다.

스로틀링을 사용하여 스크롤 이벤트 처리 함수가 일정 시간 간격으로만 실행되도록 제한할 수 있다.


2) 마우스 이동 이벤트:

마우스가 빠르게 움직일 때마다 함수를 실행하는 대신, 스로틀링을 사용하여 함수 호출 빈도를 줄일 수 있다.

 

3) 무한 스크롤 UI 구현

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Throttle</title>
    <style>
      .container {
        width: 300px;
        height: 300px;
        background-color: rebeccapurple;
        overflow: scroll;
      }

      .content {
        width: 300px;
        height: 1000vh;
      }
    </style>
  </head>

  <body>
    <div class="container">
      <div class="content"></div>
    </div>
    <div>
      일반 이벤트 핸들러가 scroll 이벤트를 처리한 횟수:
      <span class="normal-count">0</span>
    </div>
    <div>
      스로틀 이벤트 핸들러가 scroll 이벤트를 처리한 횟수:
      <span class="throttle-count">0</span>
    </div>

    <script>
      const $container = document.querySelector(".container");
      const $normalCount = document.querySelector(".normal-count");
      const $throttleCount = document.querySelector(".throttle-count");

      const throttle = (callback, delay) => {
        let timerId;
        return (event) => {
          if (timerId) return;
          // timerId가 존재할시 함수를 즉시 종료한다.(이미 타이머가 실행 중이므로)
          timerId = setTimeout(
            () => {
              callback(event);
              timerId = null;
            },
            delay,
            event
          );
          // delay 시간 후에 callback 함수를 실행하도록 예약한다.
          // callback 함수가 실행되면 timerId를 null로 설정하여, 다음 이벤트 발생시 함수를 실행할 수 있도록 한다.
          // 결과적으로, 이벤트가 연속적으로 발생하더라도 callback 함수는 delay 시간 간격으로 최대 한 번만 실행된다.
        };
      };

      let normalCount = 0;

      $container.addEventListener("scroll", () => {
        $normalCount.textContent = ++normalCount;
      });

      let throttleCount = 0;

      $container.addEventListener(
        "scroll",
        throttle(() => {
          $throttleCount.textContent = ++throttleCount;
        }, 100)
      );
    </script>
  </body>
</html>
728x90
반응형