JavaScript - 디바운스와 스로틀

김민기·2022년 9월 15일
0

JavaScript-Study

목록 보기
11/12

scroll, resize, input, mouse 같은 이벤트는 짧은 시간 간격으로 연속해서 발생한다. 이러한 이벤트에 바인딩한 이벤트 해들러는 과도하게 호출되어 성능에 문제를 일으킬 수 있다.
디바운스와 스로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 과도한 이벤트 핸들러의 호출을 방지하는 프로그래밍 기법이다.

디바운스

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

export const debounce = (callback, delay) => {
  let timerId;
  return (event) => {
    if(timerId) clearTimeout(timerId);
    timerId = setTimeout(callback, delay, event);
  }
}
const $input = document.querySelector("input");
const $msg = document.querySelector(".msg");

$input.oninput = debounce((e) => {
  $msg.textContent = e.target.value;
}, 300);

debounce 함수는 콜백 함수와 딜레이를 매개변수로 전달 받는다. 여기서 콜백 함수는 다음과 같고 딜레이는 300ms가 된다.

(e) => { $msg.textContent = e.target.value };

그리고 debounce는 함수를 리턴한다. 이 함수는 timerId를 기억하는 클로저다.

(event) => {
  if(timerId) clearTimeout(timerId);
  timerId = setTimeout(callback, delay, event);
}

input 태그의 이벤트 핸들러에 위 함수가 등록된다. 이벤트가 발생했을 때 timerId 값이 존재한다면 cleartTimeout을 실행해서 콜백 함수를 실행하지 않는다. 그리고 새롭게 timerId에 setTimeout를 사용하고 timerId 값을 대입한다. 만약 연속되는 이벤트가 없을 경우(또는 처음 입력 후 300ms 가 지났을 경우) setTimeout에 의해 콜백 함수가 실행되는데 세 번째 매개변수 event가 콜백 함수의 매개변수로 들어간다. 따라서 e.target.value로 현재 입력된 값을 받아올 수 있게된다.

디바운스는 resize 이벤트 처리나 input 요소에 입력된 값으로 ajax 요청하는 입력 필드 자동완성 UI 구현, 버튼 중복 클릭 방지 처리 등에 유용하게 사용된다.

스로틀

스로틀은 짧은 시간 간격으로 이벤트가 연속해서 발생하더라도 일정 시간 간격으로 이벤트 핸들러가 최대 한 번만 호출되도록 한다.
스로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 일정 시간 단위로 이벤트 핸들러가 호출되도록 호출 주기를 만든다.

export const throttle = (callback, delay) => {
  let tiemrId;
  return (event) => {
    if(timerId) return;
    timerId = setTimeout(() => {
      callback(event);
      timerId = null;
    }, delay, event);
  }
}
const $throttleCount = document.querySelector(".throttle-count");
let throttleCount = 0;

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

스로틀 또한 디바운스처럼 콜백 함수와 딜레이를 매개변수로 받는다. 그리고 timerId를 기억하는 클로저를 반환한다. 다만 동작이 조금 다른데, 만약 timerId가 존재할 경우 새롭게 타이머를 시작하는게 아니라 그냥 return 시켜버린다. 그리고 한번 등록된 setTimeout 콜백 함수가 실행되면 새롭게 타이머를 등록한다.

스로틀은 scroll 이벤트 처리나 무한 스크롤 UI 구현 등에 유용하게 사용된다.

디바운스와 스로틀예시

<!DOCTYPE html>
<html lang="ko">
  <head>
    <link rel="stylesheet" href="./style.css" />
    <title>Document</title>
  </head>
  <body>
    <div class="container">
      <div class="content"></div>
    </div>
    <div>
      일반 이벤트 핸들러가 scroll 이벤트를 처리한 횟수:
      <span class="normal-count">0</span>
    </div>
    <div>
      throttle 이벤트 핸들러가 scroll 이벤트를 처리한 횟수:
      <span class="throttle-count">0</span>
    </div>
    <input type="text" />
    <div class="msg"></div>
    <script src="./index.js" type="module"></script>
  </body>
</html>
export const debounce = (callback, delay) => {
  let timerId;
  console.log("event1", event);
  return (hi) => {
    console.log("event2", hi);
    if (timerId) clearTimeout(timerId);
    timerId = setTimeout(callback, delay, hi);
  };
};
export const throttle = (callback, delay) => {
  let timerId;
  return (event) => {
    if (timerId) return;
    timerId = setTimeout(
      () => {
        callback(event);
        timerId = null;
      },
      delay,
      event
    );
  };
};
import { throttle, debounce } from "./utils.js";

const $container = document.querySelector(".container");
const $noramlCount = document.querySelector(".normal-count");
const $throttleCount = document.querySelector(".throttle-count");

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

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

let throttleCount = 0;
$container.addEventListener(
  "scroll",
  throttle(() => {
    $throttleCount.textContent = ++throttleCount;
  }, 100)
);

$input.oninput = debounce((e) => {
  $msg.textContent = e.target.value;
}, 300);

0개의 댓글