클로저와 함께하는 디바운스 & 쓰로틀링
2025-02-12프론트엔드 면접을 준비하면 클로저, 디바운스, 쓰로틀링은 자주 접하게 됩니다. 꼭 면접 준비가 아니더라도 개발을 하다 보면 위 개념들은 필수적으로 접하게 되는데요.
디바운스와 쓰로틀링에 클로저가 사용된다는 걸 아셨나요?
저는 부끄럽게도 최근 lodash 라이브러리를 직접 구현하던 중 이 사실을 깨달았습니다.
1. 클로저 (Closure)
클로저는 함수가 나중에 호출되더라도 선언 당시의 외부 변수나 상태를 접근할 수 있는 기능입니다.
즉, 외부 변수나 상태는 내부 함수가 참조하고 있는 동안에는 메모리에 남아 있습니다.
예시
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()함수가 실행됩니다createCounter()함수는 종료되었지만, 내부 함수가count를 참조하고 있습니다- 따라서
count는 가비지 컬렉션 대상이 아니며, 메모리에 계속 유지됩니다 counter()를 호출할 때마다 같은count변수에 접근합니다
2. 디바운스 (Debounce)
디바운스는 동일 이벤트가 여러 번 발생하더라도 일정 시간이 지난 후에 마지막 이벤트만 실행하는 기능입니다.
사용 예시
- 검색창에서 입력이 끝난 후 API 호출
- 윈도우 리사이즈 이벤트 처리
구현
const debounce = (func: Function, delay: number) => {
let timer: NodeJS.Timeout;
return (...args: any[]) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => func(...args), delay);
};
};
클로저가 사용되는 부분
debounce 함수의 매개변수로 받은 func와 delay를 setTimeout에 등록하고, 이를 timer 변수에 할당합니다.
만약 delay 전에 이벤트가 또 발생하면 기존의 timeout을 clear하고 다시 등록하는 것이죠.
클로저 동작:
debounce함수가 실행된 후 내부 함수가 반환됩니다- 내부 함수는
timer변수를 계속 참조하고 있습니다 - 따라서
timer는 메모리에 유지되며, 여러 번 호출되어도 같은timer를 사용합니다
이 구조, 위에서 봤던 클로저 예시와 동일하죠?
3. 쓰로틀링 (Throttling)
쓰로틀링은 이벤트 발생 후 일정 시간 동안 동일 이벤트를 무시하는 기능입니다.
사용 예시
- 스크롤 이벤트 처리
- 버튼 연속 클릭 방지
디바운스와의 차이
| 항목 | 디바운스 | 쓰로틀링 |
|---|---|---|
| 동작 | 마지막 이벤트만 실행 | 일정 간격으로 실행 |
| 사용 예시 | 검색창 자동완성 | 스크롤 이벤트 |
구현
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 변수를 계속 참조하고 있습니다.
클로저 동작:
throttle함수가 실행된 후 내부 함수가 반환됩니다- 내부 함수는
lastCall변수를 계속 참조하고 있습니다 limit이 지난 후 다시 실행해도 이전의lastCall값에 접근할 수 있습니다
정리
디바운스와 쓰로틀링은 모두 클로저를 활용한 패턴입니다:
| 개념 | 핵심 메커니즘 | 클로저 활용 |
|---|---|---|
| 클로저 | 외부 변수 참조 유지 | - |
| 디바운스 | 타이머 리셋 | timer 변수를 클로저로 유지 |
| 쓰로틀링 | 시간 간격 체크 | lastCall 변수를 클로저로 유지 |
확실히 이론적으로 외우는 것보다 실제 사용하는 코드로 접근하니 더 이해가 잘 되는 것 같습니다!