클로저와 함께하는 디바운스 & 쓰로틀링
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
함수의 매개변수로 받은 func
와 delay
를 setTimeout
에 등록하고, 이를 timer
변수에 할당합니다. 만약 delay
전에 이벤트가 또 발생하면 기존의 timeout
은 clear
하고 다시 등록하는 거죠.
그런데 위 코드 구조 어디서 많이 본 구조 같지 않나요?
네! 우리가 위에서 봤던 클로저 예시와 동일합니다. 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
값에 접근할 수 있는 것입니다.
이렇듯 디바운스와 쓰로틀링은 클로저를 사용해서 구현 가능한데요. 확실히 이론적으로 외우는 것보다 실제 사용하는 코드로 접근하니 더 이해가 잘 되는 기분이네요.