back

Posts

클로저와 함께하는 디바운스 & 쓰로틀링

2025-02-04

프론트엔드 면접을 준비하면 클로저, 디바운스, 쓰로틀링은 자주 접하게 됩니다. 꼭 면접 준비가 아니더라도 개발을 하다 보면 위 개념들은 필수적으로 접하게 되는데요. 디바운스와 쓰로틀링에 클로저가 사용된다는 걸 아셨나요? 전 부끄럽게도 최근 lodash 라이브러리를 직접 구현하던 도중 이 사실을 깨달았습니다.

1. 클로저

클로저는 함수가 나중에 호출되더라도 선언 당시의 외부 변수나 상태를 접근할 수 있는 기능입니다. 즉, 외부 변수나 상태는 내부 함수가 참조하고 있는 동안에는 메모리에 남아 있습니다.

function createCounter() {
  let count = 0;  // 외부 함수의 지역 변수

  return () => {
    count += 1;  // 외부 변수 count에 접근
    console.log(`Count: ${count}`);
  };
}

const counter = createCounter();  

counter();  // Count: 1
counter();  // Count: 2
counter();  // Count: 3

위 코드에서 counter 변수가 생성되면서 createCounter() 함수는 종료됐지만, 내부 함수가 count를 참조하고 있기 때문에 count는 가비지 컬렉션 대상이 아닙니다.

2. 디바운스

디바운스는 동일 이벤트가 여러 번 발생하더라도 일정 시간이 지난 후에 마지막 이벤트만 실행하는 기능입니다.

const debounce = (func: Function, delay: number) => {
  let timer: NodeJS.Timeout;

  return (...args: any[]) => {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => func(...args), delay);
  };
};

debounce 함수의 매개변수로 받은 funcdelaysetTimeout에 등록하고, 이를 timer 변수에 할당합니다. 만약 delay 전에 이벤트가 또 발생하면 기존의 timeoutclear하고 다시 등록하는 거죠.

그런데 위 코드 구조 어디서 많이 본 구조 같지 않나요?

네! 우리가 위에서 봤던 클로저 예시와 동일합니다. debounce 함수가 실행된 후 내부 함수가 timer 변수를 계속 참조하고 있는 거죠.

3. 쓰로틀링

쓰로틀링은 이벤트 발생 후 일정 시간 동안 동일 이벤트를 무시하는 기능입니다.

const throttle = (func: Function, limit: number) => {
  let lastCall = 0;

  return (...args: any[]) => {
    const now = Date.now();
    if (now - lastCall >= limit) {
      lastCall = now;
      func(...args);
    }
  };
};

쓰로틀링에서도 동일하게 lastCall 변수를 계속 참조하고 있어서, limit이 지난 후 다시 실행해도 이전의 lastCall 값에 접근할 수 있는 것입니다.

이렇듯 디바운스와 쓰로틀링은 클로저를 사용해서 구현 가능한데요. 확실히 이론적으로 외우는 것보다 실제 사용하는 코드로 접근하니 더 이해가 잘 되는 기분이네요.