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강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.
댓글
불러오는 중...
