2025 . 오은

repo

debouncing, throttling

갑자기 debouncing, throttling 을 직접 만들어 보고 싶었습니다. 클로저와 고차 함수를 사용하면 될 것 같았습니다. 쓸일은 없을것 같지만 ㅎㅎ

0. Closure + HOF

  • 클로저: 이벤트가 발생할 때마다 이전의 상태를 기억해야 하기 때문에 클로저가 필요. 반환된 함수가 실행될 때마다 클로저를 사용하여 함수 실행여부를 결정할 수 있음
  • 고차 함수: 인자로 실행하려는 함수를 받아, 이 함수를 조건에 따라 실행하는 새로운 함수를 반환

1. Debouncing

type Debounce = (...args: any[]) => void;
// 콜백함수와 딜레이를 인자로 받는 HOC
const debounce = (func: (...args: any[]) => void, delay: number): Debounce => {
  let timer : NodeJS.Timeout | null = null; // 클로저로 유지될 변수(타이머)
  return (...args : any[]) => {
    if(timer !== null) clearTimeout(timer); // 이전 타이머가 있으면 취소
    timer = setTimeout(() => {
      func(...args); // 일정시간 후 실행, this 참조 대비하여 apply
    }, delay);
  };
}
// HOC 에 함수와 딜레이를 넣어서 호출
const handleInput: Debounce = debounce(() => {
  console.log('API 호출');
}, 300);

document.querySelector('input').addEventListener('input', handleInput);

2. Throttling

type Throttle = (...args: any[]) => void;
// 콜백함수와 리미트를 인자로 받는 HOC
const throttle = (func: (...args: any[]) => void, limit: number): Throttle => {
  let lastTimer: NodeJS.Timeout | null = null; // 클로저로 유지될 변수(타이머)
  let lastRan: number | null = null; // 클로저로 유지될 변수(마지막 실행시점)
  return (...args: any[]) => {
    if(lastRan !== null){
      func(...args); // 처음 호출될 때 바로 실행
      lastRan = Date.now(); // 마지막 실행 시점을 현재 시간으로 기록
      return;
    } 
    if(lastTimer !== null) clearTimeout(lastTimer); // 이전에 설정된 타이머 취소
    lastTimer = setTimeout(() => {
      if((Date.now() - lastRan) >= limit) { // 제한 시간 이후에만 실행
        func(...args); // 콜백함수 실행
        lastRan = Date.now(); // 마지막 실행 시점 업데이트
      } 
    }, limit - (Date.now() - lastRan))
    // 위 setTimeout의 2번째 인자 부분은,
    // 마지막으로 함수가 실행된 시점(lastRan)에서 얼마나 시간이 지났는지 계산하여, 
    // 정확히 limit 간격만큼 대기한 후에 함수를 실행시키기 위한 계산임
  };
}

// HOC 에 함수와 리미트를 넣어서 호출
const handleScroll: Throttle = throttle(() => {
  console.log("스크롤 이벤트 처리");
}, 1000);

window.addEventListener("scroll", handleScroll);