함수를 당장 실행하지 않고 딜레이 후 실행하고 싶을 때 구현할 수 있는 두 가지 메서드가 있다.
- setTimeout - 설정해놓은 최소 시간 이후 함수가 한번 실행된다.
- setInterval - 설정해놓은 최소 시간 마다 일정 간격으로 함수가 주기적으로 실행된다.
setTimeout
문법
let example = setTimeout(function|code, [delay], [arg1], [arg2], ...);
파라미터
function | code는 실행을 위한 함수나 문자열이다. 주로 함수를 받으며, 코드의 문자열도 넘길 수 있지만 권장하지 않는다.
delay는 실행하기 전의 딜레이 즉, 최소 시간이다. ms단위로 이루어져 있으며, 1000ms = 1s이다. 기본값은 0이다.
arg1, arg2..는 함수에 대한 인자(arguments)들이다. (IE9 미만 버전에서는 지원되지 않는다.)
예)
// 최소 시간 1초 후에 example()를 호출하는 코드
function example() {
alert('Hello');
}
setTimeout(example, 1000);
// 인자 주는법
function example(argOne, argTwo) {
alert(argOne + 'Hello' + argTwo);
}
setTimeout(example, 1000, 'Tim', 'Good!'); // Tim Hello Good!
만일 첫번째 인자에 문자열이 들어온다면, 자바스크립트는 그로부터 함수를 만들어낸다.
// 문자열보다는
setTimeout("alert('Hello')", 1000);
// 함수 사용이 좋다.
setTimeout(() => alert('Hello'), 1000);
함수를 넘길 땐 () 괄호를 붙여주면 안 된다.
// 잘못된 코드
setTimeout(example() ,1000);
example()는 함수를 실행시키고 그 결과가 setTimeout으로 전달된다. example()의 결과는 undefined이기 때문에 작동하지 않는다.
clearTimeout
setTimeout을 호출했을 때, clearTimeout을 사용해 반환을 취소할 수도 있다. setTimeout을 호출하면 타이머 식별자(timer identifier)가 반환되는데 스케줄링을 취소하고 싶을 땐 이 식별자(아래 예에서는 example)를 사용하면 된다.
let example = setTimeout(...);
clearTimeout(example);
let example = setTimeout(() => console.log('Hello'), 1000);
console.log(example); // Hello
clearTimeout(example); // 스케쥴링 후 취소
console.log(example); // 취소 후 실행해도 반환되지 않는다.
위 코드를 실행하면 브라우저 환경에선 타이머 식별자가 숫자라는 것을 알 수 있다. 다른 호스트 환경에선 타이머 식별자가 숫자형 이외의 자료형일 수 있다. Node.js 같은 경우는 setTimeout을 실행하면 타이머 객체가 반환된다.
브라우저는 HTML5 timers section을 준수하고 있다.
setInterval
setInterval은 setTimeout과 동일한 문법을 사용한다.
문법
let example = setInterval(function|code, [delay], [arg1], [arg2], ...);
다만, setTimeout은 함수를 단 한 번만 실행하는 시키는 반면 setInterval은 함수를 설정한 최소 시간에 맞춰 일정하게 실행한다.
함수 호출을 중지시키려면 clearInterval(example)을 사용하면 된다.
// 3초 간격으로 메세지를 띄운다.
let example = setInterval(() => alert('출력'), 3000);
// 7초 후에 정지
setTimeout(() => { clearInterval(example); alert('중지'); }, 7000);
* Chrome, Firefox 등 대부분의 브라우저는 alert/confirm/prompt 창이 떠 있는 동안에도 내부 타이머를 멈추지 않는다.
중첩 setTimeout
무언가를 일정 간격을 두고 실행하는 방법에는 setInterval과 setTimeout을 이용하는 두 가지 방법이 있다.
// setInterval 사용하지않고 중첩 setTimeout사용
let example = setTimeout(function test() {
alert('실행');
example = setTimeout(test, 3000);
}, 3000);
두 번째 setTimeout은 실행이 종료되면 다음 호출을 스케줄링한다. 중첩 setTimeout은 호출 결과에 따라 다음 호출을 원하는 방식으로 조정해 스케줄링할 수 있기 때문에 setInterval을 사용하는 것보다 유연하다.
예) 5초 간격으로 서버에 요청을 보내 데이터를 얻는다고 가정할 때, 서버가 과부하 상태라면 요청 간격을 10초, 20초, 40초 등으로 증가시켜주는 게 좋다.
let delay = 5000;
let timerId = setTimeout(function request() {
...요청 전송...
if (서버 과부하로 인한 요청이 실패한다면) {
// 다음 요청 간격을 늘린다.
delay *= 2;
}
timerId = setTimeout(request, delay);
}, delay);
그리고 주기적으로 CPU 사용량이 많은 작업(CPU-hungry tasks)이 있다면, 실행에 걸린 시간을 측정하고 다음 호출 간격을 어떻게 할지 계획할 수 있다.
재귀적인 setTimeout은 setInterval과 달리 실행 간 딜레이를 보장한다.
// setInterval
let i = 1;
setInterval(function () {
func(i);
}, 100);
// setTimeout
let i = 1;
setTimeout(function run() {
func(i);
setTimeout(run, 100);
}, 100);
setInterval을 사용하면 func 호출 사이의 지연 간격이 실제 설정한 100ms보다 짧아진다. func을 실행하는 데 소모되는 시간도 지연 간격에 포함시키기 때문이다. 반대로 func을 실행하는 데 걸리는 시간이 실제 설정한 지연 간격보다 길다면 엔진이 func의 실행이 종료될 때까지 기다려준다. func의 실행이 종료되면 엔진은 스케줄러를 확인하고, 지연 시간이 지났으면 다음 호출을 바로 시작한다. 따라서 함수 호출에 걸리는 시간이 매번 delay 밀리 초보다 길면, 모든 함수가 쉼 없이 계속 연속 호출된다.
중첩 setTimeout을 이용하면 다음과 같이 실행 흐름이 이어진다.
중첩 setTimeout을 사용하면 실제 설정한 지연(설정: 100ms)이 보장된다. 이전 함수의 실행이 종료된 이후에 다음 함수 호출에 대한 계획이 세워지기 때문에 지연 간격이 보장된다.
setInterval·setTimeout과 가비지 컬렉션
setInterval이나 setTimeout에 함수를 넘기면, 함수에 대한 내부 참조가 새롭게 만들어지고 이 참조 정보는 스케줄러에 저장된다. 따라서 해당 함수를 참조하는 것이 없어도 setInterval과 setTimeout에 넘긴 함수는 가비지 컬렉션의 대상이 되지 않는다.
// 스케줄러가 함수를 호출할 때까지 함수는 메모리에 유지된다.
setTimeout(function() {...}, 100);
외부 렉시컬 환경을 참조하는 함수가 있다고 가정해본다면, 이 함수가 메모리에 남아있는 동안엔 외부 변수 역시 메모리에 남아있게 된다. 그렇게 된다면 실제 함수가 차지했어야 하는 공간보다 더 많은 메모리 공간이 사용되므로 이런 부작용을 방지하고 싶다면 스케줄링할 필요가 없어진 함수는 아무리 작더라도 취소하도록 하는 게 좋다.
최소 시간이 0인 setTimeout
setTimeout(func, 0)이나 setTimeout(func)을 사용하면 setTimeout의 최소 시간을 0으로 설정할 수 있다.
최소 시간을 0으로 설정하면 func을 가능한 한 빨리 실행할 수 있다. 다만, 스케줄러는 현재 실행 중인 스크립트의 처리가 종료된 이후에 스케줄링한 함수를 실행한다. 이런 특징을 이용하면 현재 스크립트의 실행이 종료된 직후에 원하는 함수가 실행될 수 있게 할 수 있다.
setTimeout(() => alert('Two'));
alert('One');
예에서 첫 번째 코드는 0초 후에 함수 호출을 계획표에 기록해주는 역할을 한다. 하지면 여기서 스케줄러는 현재 스크립트(alert함수)의 실행이 종료되고 나서 계획표에 들어있는 것을 실행하므로 One이 먼저 실행되고 Two가 다음에 실행된다.
* 브라우저 환경에서 실제 최소 시간은 0이 아니다.
브라우저는 HTML5 표준에서 정한 중첩 타이머 실행 간격 관련 제약을 준수한다. 해당 표준엔 "다섯 번째 중첩 타이머 이후엔 대기 시간을 최소 4밀리 초 이상으로 강제해야 한다." 라는 제약이 명시되어 있다. 즉, setTimeout과 setInterval은 처음 몇 번은 함수를 지연 없이 실행하지만 나중엔 지연 간격을 4밀리초 이상으로 늘려버린다. 한편, 서버 측엔 이런 제약이 없다. Node.js의 process.nextTick과 setImmediate를 이용하면 비동기 작업을 지연없이 실행할 수 있다. (위 언급된 제약은 브라우저에 한정)
requestAnimationFrame
자바스크립트에서 애니메이션을 구현하는 방법으로 new Date()를 사용한 타이머 함수를 만들어 사용하게 된다. 즉, 시작 시점과 종료 시점을 직접 변수에 저장해 반복 실행하는 방법이다. 단점으로는 불필요한 콜 스택이 지나치게 많다는 점과 표시 가능한 주사율에 영향을 받으므로 지나치게 높은 부하가 발생할 수 있다는 점이다. 이런 경우 필요한 방법이 requestAnimationFrame(반복할 함수)이다.
requestAnimationFrame()의 사용 이유는
- 백그라운드 동작 및 비활성화 시 중지(성능 최적화)
- 최대 1ms로 제한되며 1초에 60번 동작
- 다수의 애니메이션에도 각각 타이머 값을 생성 및 참조하지 않고 내부의 동일한 타이머 참조
등이 있다.
예)
const test = () => {
console.log("test");
requestAnimationFrame(test);
};
requestAnimationFrame(test);
// "test" 게속 출력
위 코드를 실행하면 "test"가 계속해서 실행되게 된다. 그러면 연속해서 루프가 생성되므로 Maximum callstack이 발생하는 문제가 생긴다.
이러한 문제를 해결하는 방법은 조건을 걸어주는 등 다양한 방법이 있다.
!(function() {
let start = new Date().getTime();
let callback = function() {
let ts = new Date().getTime();
if (ts - 1000 > start) {
// console.log('End');
}
else {
console.log(ts);
requestAnimationFrame(callback);
}
}
requestAnimationFrame(callback);
})();
이런 식으로 코드를 실행하면 무제한 호출되는 것이 아닌 60번만 제한해 호출하게 된다.
cancelAnimationFrame
requestAnimationFrame()를 취소하는 방법으로 cancelAnimationFrame()를 사용한다.
test = requestAnimationFrame(callback);
cancelAnimationFrame(test);
'Javascript' 카테고리의 다른 글
JavaScript - Callback (0) | 2021.02.28 |
---|---|
JavaScript - Message Queue and Event Loop (0) | 2020.09.18 |
JavaScript - IIFE, Modules and Namespaces (0) | 2020.09.18 |
JavaScript - Expression vs Statement(표현식과 문장) (0) | 2020.09.16 |
JavaScript - Function, Block, Lexical Scope (0) | 2020.09.15 |
댓글