디바운스와 스로틀

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

다음 예제의 버튼을 짧은 시간 간격으로 연속해서 클릭했을 때 일반적인 이벤트 핸들러와 디바운스, 스로틀된 이벤트 핸들러의 호출 빈도가 어떻게 다른지 살펴보자!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button>click me</button>
    <pre>일반 클릭 이벤트 카운터    <span class="normal-msg">0</span></pre>
    <pre>디바운스 클릭 이벤트 카운터    <span class="debounce-msg">0</span></pre>
    <pre>스로틀 클릭 이벤트 카운터    <span class="throttle-msg">0</span></pre>
    <script>
        const $button = document.querySelector('button');
        const $normalMsg = document.querySelector('.normal-msg');
        const $debounceMsg = document.querySelector('.debounce-msg');
        const $throttleMsg = document.querySelector('.throttle-msg');

        const debounce = (callback, delay) => {
            let timerId;
            return event => {
                if(timerId) clearTimeout(timerId);
                timerId = setTimeout(callback,delay,event);
            };
        };

        const throttle = (callback, delay) => {
            let timerId;
            return event => {
                if(timerId) return;
                timerId = setTimeout(()=>{
                    callback(event);
                    timerId = null;
                },delay,event);
            };
        };

        $button.addEventListener('click',()=>{
            $normalMsg.textContent = +$normalMsg.textContent + 1;
        });

        $button.addEventListener('click',debounce(()=>{
            $debounceMsg.textContent = +$debounceMsg.textContent + 1;
        },500));

        $button.addEventListener('click',throttle(()=>{
            $throttleMsg.textContent = +$throttleMsg.textContent + 1;
        },500));
    </script>
</body>
</html>

일반적인 이벤트 핸들러

'click me' 버튼을 누를때마다 카운터가 +1 된다.

디바운스 이벤트 핸들러

디바운스는 delay보다 짧은 간격으로 이벤트가 연속해서 발생하면 debounce 함수의 첫 번째 인수로 전달한 콜백 함수는 호출되지 않다가 input 이벤트가 더 이상 발생하지 않으면 한 번만 호출된다. 즉, 디바운스는 짧은 시간 간격으로 발생하는 이벤트를 그룹화해서 마지막에 한 번만 이벤트 핸들러가 호출되도록 한다.

사용되는 곳

  • resize 이벤트 처리
  • input 요소에 입력된 값으로 ajax 요청하는 입력 필드 자동완성 UI 구현
  • 버튼 중복 클릭 방지 처리

( 실무에서는 Underscore의 debounce 함수나 Lodash의 debounce 함수를 사용하는 것을 권장 )

스로틀 이벤트 핸들러

스로틀은 throttle 함수에 두 번째 인수로 전달한 시간(delay)이 경과하기 이전에 이벤트가 발생하면 아무것도 하지 않다가 delay 시간이 경과했을 때 이벤트가 발생하면 콜백 함수를 호출하고 새로운 타이머를 재설정한다. 즉, 스로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 일정 시간 단위로 이벤트 핸들러가 호출되도록 호출 주기를 만든다.

사용되는 곳

  • scroll 이벤트 처리
  • 무한 스크롤 UI 구현

( 실무에서는 Underscore의 throttle 함수나 Lodash의 throttle 함수를 사용하는 것을 권장 )