자바스크립트 - 타이머 함수
호출 스케줄링
함수는 함수를 호출했을 때 즉시 실행된다.
즉시 실행이 아닌 일정 시간 이후에 호출되도록 하려면 타이머 함수를 사용하면 된다.
이를 호출 스케줄링이라 한다.
자바스크립트는 타이머를 생성할 수 있는 타이머 함수 setTimeout(한 번만 동작)과 setInterval(여러번 동작), 타이머를 제거할 수 있는 타이머 함수 clearTimeout과 clearInterval을 제공한다.
타이머 함수는 일정 시간이 경과된 이후 콜백 함수가 호출되도록 타이머를 생성하는 것이다.
타이머 함수는 빌트인 함수가 아닌 호스트 객체(자바스크립트 실행환경에서 추가로 제공하는 객체)이다.
자바스크립트는 싱글 스레드이기 때문에 타이머 함수는 비동기 방식으로 동작한다.
타이머 함수
setTimeout함수는 두 번째 인수로 전달받은 시간으로 한 번만 동작하는 생성 한다. 그 후 타이머가 만료되면 첫 번째 인수로 전달받은 콜백함수가 호출된다.
setTimeout
setTimeout(() => console.log('hi'), 1000) //1초(1000ms)뒤에 hi 출력
setTimeout( name => console.log(`hi ${name}`), 1000, 'lee') //1초(1000ms)뒤에 hi lee 출력
밑의 코드 방식처럼 전달해야 할 인수가 존재하는 경우 타이머 뒤에 써서 전달할 수 있다.
2번째 인수로 전달받은 시간의 경우 4ms 이하는 최소 지연 시간인 4ms로 작동한다.
그래서 궁금증이 생겼다. 시간초를 0과 401로 할 경우 어떻게 처리될까?
function a() {
return console.log("a");
}
function b() {
return console.log("b");
}
function c() {
return console.log("c");
}
function d() {
return console.log("d");
}
function slow() {
console.log("slow");
}
function fast() {
console.log("fast");
}
setTimeout(slow, 401); /* fast fase
setTimeout(fast, 0); slow a
setTimeout(a, 400); a b
setTimeout(b, 400); b c
setTimeout(c, 400); c d
setTimeout(d, 400); d slow */
여러 번을 테스트 했는데 결과가 왼쪽 오른쪽으로 다르게 나왔다. 그래서 검색을 해보니 브라우저 환경 등 갖가지 요소로 인해 딜레이가 돼서 값이 바뀔 수도 있다고 한다.
setInterval
let count = 1
const timeoutId = setInterval(() => {
console.log(count); // 1 2 3 4 5
if (count++ === 5) clearInterval(timeoutId)
}, 1000);
// count가 5면 setInterval함수가 반환한 타이머id를 clearInterval 함수의 인수로 전달하여
// 타이머를 취소한다. 타이머가 취소되었기 때문에 5이후로는 출력이 되지 않는다.
디바운스와 스로틀
디바운스와 스로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 과도한 이벤트 핸들러의 호출을 방지하는 프로그래밍 기법이다.
디바운스
디바운스는 짧은 시간 간격으로 이벤트가 연속해서 발생하면 이벤트 핸들러를 호출하지 않다가 일정 시간이 경과한 이후에 이벤트 핸들러가 한 번만 호출되도록 한다.
즉, 짧은 간격으로 여러 번 발생하는 이벤트를 즉시 처리하지 않고 그룹화해서 묶어 놨다가 마지막에 한 번만 이벤트 핸들러가 호출하는 것이다.
function debounce(func, wait) {
let timeout;
return function executed(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
const handleResize = debounce(function() {
console.log('리사이징 이벤트 처리:', window.innerWidth);
}, 250);
window.addEventListener('resize', handleResize);
- `debounce` 함수는 원래 함수(`func`)와 대기 시간(`wait`)을 매개 변수로 받습니다.
- 내부 함수 `executed`이 이벤트 리스너 등에 의해 호출될 때, `setTimeout`을 사용해 원래 함수의 실행을 예약합니다.
- 만약 `wait` 시간 내에 `executed`가 다시 호출되면, `setTimeout`에 의해 예약된 원래 함수의 실행이 취소됩니다.
- `wait` 시간이 지난 후 추가적인 호출이 없으면 `later` 함수가 실행되어 원래의 함수가 호출됩니다.
이런 방식을 통해 일정시간 내에 여러 번 클릭 될 경우 `clearTimeout`을 통해 그 전의 실행을 취소하다가 마지막 클릭
즉, 시간 내에 아무 클릭이 없을 경우의 마지막 클릭 때 함수가 호출된다.
디바운스는 사용자 입력 처리, 윈도우 리사이징, 스크롤 이벤트 처리, 버튼 중복 클릭 방지등에 유용하게 사용된다.
스로틀
스로틀은 짧은 시간 간격으로 이벤트가 연속해서 발생하더라도 일정 시간 간격으로 이벤트 핸들러가 최대 한 번만 호출되도록 한다.
즉, 일정 시간 단위로 그룹화 하여 그 시간 단위마다 한 번씩 이벤트 핸들러가 호출되도록 주기를 만들어 준다.
function throttle(callback, delay) {
let lastCall = 0;
return function() {
const now = new Date().getTime();
if (now - lastCall < delay) return;
lastCall = now;
callback.apply(this, arguments);
};
}
function handleScroll() {
console.log('스크롤 이벤트 처리');
}
const throttledHandleScroll = throttle(handleScroll, 500); // 500밀리초 간격으로 스로틀링
window.addEventListener('scroll', throttledHandleScroll);
1. `throttle`함수는 스로틀링할 `callback` 함수와 시간 간격을 정의하는 `delay`를 매개 변수로 받습니다.
2. 처음 `lastcall`은 0으로 초기화합니다. `now`에는 함수가 호출될 때마다의 시간을 변수에 저장합니다.
3. `now - lastcall`이 `delay` 보다 작다면 함수 실행을 중지하고 반환해 자주 함수가 호출되는 것을 방지합니다.
4. `delay`보다 크다면 `lastcall`을 `now`로 다시 저장한 후 `callback`함수를 호출합니다.
이런 방식을 통해 일정 시간 내에 들어온 클릭을 한번에 묶어서 일정 시간마다 한 번만 호출한다.
디바운스와 스로틀은 버스로 예시를 들면 이해하기 쉽다.
디바운스 상황
- 버스가 정류장에 도착합니다.
- 승객 한 명이 탈 때마다 버스는 5초씩 대기합니다.
- 만약 마지막 승객이 탑승하고 5초 동안 다른 승객이 탑승하지 않으면 버스는 출발합니다.
- 그러나 5초 안에 다른 승객이 탑승하면 다시 5초를 기다립니다.
스로틀 상황
- 버스가 정류장에 도착합니다.
- 승객이 탑승하든 탑승하지 않든 버스는 10초에 한 번씩 출발합니다.
- 버스가 출발하면 바로 다음 버스가 도착합니다.
- 10초 후에 탑승하려면 다음 버스를 탑승해야 합니다.