모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

22강 / 전체 22강

타이머

11분 읽기 조회 5

setTimeout으로 지연 실행, setInterval로 반복 실행, clearTimeout/clearInterval로 취소, 디바운스·스로틀 패턴, requestAnimationFrame으로 부드러운 애니메이션까지 타이머를 완전히 다룹니다.

🎯 학습 목표

이 강을 마치면 다음을 할 수 있습니다.

  • setTimeout으로 일정 시간 후 코드를 실행하고 clearTimeout으로 취소할 수 있습니다.
  • setInterval로 주기적으로 코드를 반복 실행하고 clearInterval로 정지할 수 있습니다.
  • 디바운스와 스로틀 패턴을 구현할 수 있습니다.
  • requestAnimationFrame으로 브라우저에 최적화된 애니메이션을 만들 수 있습니다.

⏱️ setTimeout — 지연 실행

이 섹션에서는 특정 시간이 지난 뒤 한 번 코드를 실행하는 setTimeout을 살펴보겠습니다.

setTimeout(callback, delay)은 delay 밀리초(ms) 후에 콜백 함수를 한 번 실행합니다. 1초 = 1000ms입니다. 자바스크립트는 단일 스레드로 동작하므로, setTimeout은 코드 실행을 차단하지 않습니다. 타이머를 설정하고 다음 코드를 즉시 계속 실행합니다. 이는 23강에서 배울 비동기 프로그래밍의 핵심 개념입니다.

// 기본 사용법
console.log("시작");

const timerId = setTimeout(() => {
  console.log("2초 후 실행!");
}, 2000);  // 2000ms = 2초

console.log("종료");  // "종료"가 먼저 출력됨!
// 출력 순서: "시작" → "종료" → (2초 후) "2초 후 실행!"

// 타이머 취소 — 실행 전에만 가능
clearTimeout(timerId);
// 이제 "2초 후 실행!"은 출력되지 않음

// 인수 전달
setTimeout((name, count) => {
  console.log(`${name}: ${count}번 실행`);
}, 1000, "홍길동", 3);  // 세 번째 인수부터 콜백으로 전달

// 실전: 알림 토스트 자동 닫기
function showToast(message) {
  const toast = document.createElement("div");
  toast.className = "toast";
  toast.textContent = message;
  document.body.append(toast);

  // 3초 후 자동 제거
  setTimeout(() => {
    toast.classList.add("fade-out");
    setTimeout(() => toast.remove(), 300);  // 애니메이션 후 제거
  }, 3000);
}

showToast("저장되었습니다!");

// delay 0 — 이벤트 루프에서 다음 기회에 실행
setTimeout(() => {
  console.log("나중에 실행");
}, 0);
console.log("먼저 실행");  // 이게 먼저 출력됨

delay가 0이라도 즉시 실행되지 않습니다. 현재 실행 중인 코드가 모두 완료된 뒤 이벤트 루프의 다음 기회에 실행됩니다. 이 미묘한 동작은 23강의 비동기 개념에서 이벤트 루프를 배울 때 명확해집니다. setTimeout이 반환하는 타이머 ID를 변수에 저장해야 나중에 취소할 수 있습니다. 특히 컴포넌트가 제거될 때 타이머를 정리하지 않으면 메모리 누수가 발생합니다.

🔁 setInterval — 반복 실행

이 섹션에서는 일정 간격으로 코드를 반복 실행하는 setInterval을 살펴보겠습니다.

setInterval(callback, interval)은 콜백을 매 interval 밀리초마다 반복해서 실행합니다. clearInterval(id)로 정지할 때까지 계속 실행됩니다.

// 기본 사용법
const intervalId = setInterval(() => {
  console.log(new Date().toLocaleTimeString());
}, 1000);

// 5초 후 정지
setTimeout(() => {
  clearInterval(intervalId);
  console.log("타이머 정지");
}, 5000);

// 실전: 디지털 시계
const clockEl = document.querySelector("#clock");

function updateClock() {
  const now = new Date();
  const h = now.getHours().toString().padStart(2, "0");
  const m = now.getMinutes().toString().padStart(2, "0");
  const s = now.getSeconds().toString().padStart(2, "0");
  clockEl.textContent = `${h}:${m}:${s}`;
}

updateClock();  // 즉시 한 번 실행 (setInterval은 interval 후에 첫 실행)
const clockTimer = setInterval(updateClock, 1000);

// 실전: 카운트다운 타이머
function countdown(seconds, onTick, onEnd) {
  let remaining = seconds;
  onTick(remaining);

  const timer = setInterval(() => {
    remaining--;
    onTick(remaining);

    if (remaining <= 0) {
      clearInterval(timer);
      onEnd();
    }
  }, 1000);

  return timer;  // 외부에서 취소 가능하도록 ID 반환
}

countdown(
  10,
  (sec) => console.log(`남은 시간: ${sec}초`),
  () => console.log("완료!")
);

setInterval의 중요한 특성이 있습니다. 첫 번째 실행은 interval이 지난 후에야 됩니다. 즉시 실행하려면 setInterval 전에 함수를 한 번 직접 호출해야 합니다. 또한 콜백 실행 시간이 interval보다 길어지면 간격이 밀릴 수 있습니다. 정밀한 타이밍이 필요하다면 setTimeout을 재귀적으로 호출하는 방식이 더 정확합니다. setInterval은 페이지를 벗어나거나 컴포넌트가 제거될 때 반드시 clearInterval을 호출해야 합니다. 그렇지 않으면 메모리 누수와 예상치 못한 동작이 계속됩니다.

🛡️ 디바운스와 스로틀

이 섹션에서는 타이머를 활용해 함수 호출 빈도를 제어하는 두 가지 핵심 패턴을 살펴보겠습니다.

사용자 입력, 스크롤, 창 크기 조절처럼 이벤트가 매우 빠르게 발생하는 상황에서 모든 이벤트에 반응하면 성능이 저하됩니다. 디바운스(Debounce)와 스로틀(Throttle)은 함수 실행 빈도를 제어하는 패턴입니다.

// 디바운스 — 마지막 호출 후 일정 시간이 지나야 실행
// 사용 사례: 검색 자동완성, 입력값 저장
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);  // 이전 타이머 취소
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

const searchInput = document.querySelector("#search");

const handleSearch = debounce((e) => {
  console.log(`검색: ${e.target.value}`);  // 타이핑 멈추고 500ms 후 실행
  // API 호출
}, 500);

searchInput.addEventListener("input", handleSearch);
// "자바", "자바스", "자바스크" ... 타이핑하면
// 마지막 타이핑 후 500ms가 지나야 검색 실행

// 스로틀 — 일정 시간마다 최대 한 번 실행
// 사용 사례: 스크롤 이벤트, 마우스 이동, 게임 키 입력
function throttle(fn, interval) {
  let last = 0;
  return function(...args) {
    const now = Date.now();
    if (now - last >= interval) {
      last = now;
      fn.apply(this, args);
    }
  };
}

const handleScroll = throttle(() => {
  console.log(`스크롤: ${window.scrollY}px`);
}, 200);  // 200ms마다 최대 1번 실행

window.addEventListener("scroll", handleScroll);
// 스크롤 이벤트는 초당 수십 번 발생하지만
// 200ms에 한 번만 처리

디바운스 vs 스로틀 선택 기준을 명확히 하겠습니다. 디바운스는 연속 이벤트 중 마지막만 처리할 때 씁니다. 검색 자동완성처럼 사용자가 입력을 멈추면 한 번만 검색하는 경우에 적합합니다. 스로틀은 연속 이벤트를 일정 간격으로 제한할 때 씁니다. 스크롤 위치 추적이나 무한 스크롤처럼 중간 과정도 처리해야 할 때 적합합니다. 두 함수를 직접 구현하면 동작 원리를 이해할 수 있지만, 실무에서는 lodash 라이브러리의 _.debounce, _.throttle을 자주 사용합니다.

🎬 requestAnimationFrame — 부드러운 애니메이션

이 섹션에서는 브라우저 렌더링 사이클에 맞춰 최적화된 애니메이션을 만드는 requestAnimationFrame을 살펴보겠습니다.

CSS 트랜지션이나 CSS 애니메이션으로 해결할 수 없는 복잡한 애니메이션은 자바스크립트로 만들어야 합니다. 이 때 setInterval보다 requestAnimationFrame(rAF)이 훨씬 좋습니다. 브라우저가 다음 화면을 그리기 직전에 콜백을 실행해주기 때문에 보통 초당 60번(60fps) 실행되며, 탭이 비활성화되면 자동으로 멈춰 배터리를 절약합니다.

// 기본 패턴 — 재귀 호출로 반복
const box = document.querySelector(".box");
let position = 0;
let rafId;

function animate() {
  position += 2;  // 매 프레임 2px 이동
  box.style.transform = `translateX(${position}px)`;

  if (position < 400) {
    rafId = requestAnimationFrame(animate);  // 다음 프레임에 다시 실행
  }
}

rafId = requestAnimationFrame(animate);  // 첫 프레임 시작

// 취소
cancelAnimationFrame(rafId);

// 시간 기반 애니메이션 (더 정확)
// rAF 콜백은 DOMHighResTimeStamp를 인수로 받음
let startTime = null;
const duration = 1000;  // 1초 동안

function animateByTime(timestamp) {
  if (!startTime) startTime = timestamp;
  const elapsed = timestamp - startTime;          // 경과 시간
  const progress = Math.min(elapsed / duration, 1); // 0 ~ 1

  box.style.transform = `translateX(${progress * 400}px)`;  // 0 ~ 400px

  if (progress < 1) {
    requestAnimationFrame(animateByTime);
  } else {
    console.log("애니메이션 완료");
  }
}

requestAnimationFrame(animateByTime);

시간 기반 애니메이션에서 progress(0~1 사이 값)에 목표값을 곱하는 패턴은 매우 유용합니다. 이 값에 이징 함수(easing function)를 적용하면 자연스러운 움직임도 만들 수 있습니다. 예: const eased = progress * (2 - progress)(ease-out 효과). 대부분의 경우 CSS 트랜지션/애니메이션이 더 성능이 좋으므로 JavaScript 애니메이션은 CSS로 해결할 수 없는 복잡한 시나리오에 사용합니다.

📝 핵심 요약

22강에서 배운 내용을 정리해 보겠습니다.

API용도취소
setTimeout(fn, ms)ms 후 한 번 실행clearTimeout(id)
setInterval(fn, ms)ms마다 반복 실행clearInterval(id)
requestAnimationFrame(fn)다음 프레임에 실행 (60fps)cancelAnimationFrame(id)
디바운스마지막 이벤트 후 실행검색, 자동 저장
스로틀주기적으로 최대 1번스크롤, 리사이즈

다음 강인 23강 — 비동기 개념에서는 자바스크립트가 한 번에 하나의 작업만 처리하면서도 어떻게 비동기 작업(네트워크 요청, 파일 읽기 등)을 효율적으로 다루는지 이벤트 루프 원리부터 배웁니다. 22강의 타이머가 왜 즉각적으로 실행되지 않는지, 그 근본 이유를 23강에서 명확하게 이해하게 됩니다!

관련 주제

  • setTimeout
  • setInterval
  • clearTimeout clearInterval
  • 디바운스
  • 스로틀
  • requestAnimationFrame
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 자바스크립트 기초 30강 — 웹을 움직이는 언어
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

자바스크립트 기초 30강 — 웹을 움직이는 언어

이 강의가 속한 시리즈는 총 22강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...