June 29, 2020
33-js-concepts를 스터디하며 정리한 포스트 입니다.
자바스크립트에서는 스케쥴링을 위한 타이머 함수가 존재한다.
사용가능한 타이머 함수와 타이머 함수의 기능은 아래와 같다.
| 함수 이름 | 기능 |
|---|---|
| setTimeout(fn, timeout) | 일정 시간 후 fn으로 받은 함수를 실행한다. |
| setInterval(fn, interval) | fn으로 받은 함수를 일정 시간마다 실행한다. |
| clearTimeout(id) | 실행되고 있는 id값의 timeout을 중지 |
| clearInterval(id) | 실행되고 있는 id값의 interval을 중지 |
타이머 함수는 함수를 당장 실행하지 않고 일정 시간 후 실행하고자 할 때 사용한다.
이와 같은 상황을 호출 스케쥴링(scheduling a call)이라고 한다.
타이머 함수는 자바스크립트의 스펙이 아닌 브라우저와 node.js에서 제공한다.
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...);func|codesetTimeout에서 실행을 할 함수나 문자열을 받는다.
주로 함수를 받으나 코드의 문자열도 받을 수 있지만 권장 사항은 아니다.
delay파라미터로 받은 func|code를 실행하기전 딜레이를 의미한다.
ms단위로 이루어져 있으며 1000ms는 1s와 같다.
기본 설정 값은 0이 들어있다.
arg1, arg2 …함수에 대한 인자를 의미한다. (IE9 아래의 버전에서는 지원하지 않는다.)
1초 뒤에 sayHello()를 호출하는 코드를 아래와 같이 작성할 수 있다.
function sayHello() {
alert('Hello!')
}
setTimeout(sayHello, 1000)위의 코드를 실행시켜보면 약 1초뒤에 Hello!라는 alert이 보이게 된다.
함수에 인자가 있는 경우 arg1, arg2 …를 이용해 함수에 인자를 전달할 수 있다.
function sayHello(name) {
alert('Hello ' + name + '!')
}
setTimeout(sayHello, 1000, 'Minsu')넘겨준 Minsu라는 인자가 sayHello함수에 잘 전달되는 것을 확인할 수 있다.
func|code인자에서 함수가 아닌 문자열이 들어가게 될 경우도 작동한다.
자바스크립트는 문자열을 통해 함수를 만들어내 실행하게 된다.
setTimeout('alert("Hello")', 1000)정상적으로 작동하지만 권장되는 방식은 아니다.
setTimeout의 인자로 함수를 넘겨주어야 한다.
하지만 함수를 넘겨주어야 하지만 아래와 같이 함수를 실행해서는 안된다.
function sayHello() {
alert('Hello!')
}
setTimeout(sayHello(), 1000) // Wrong!위의 코드는 작동하지 않는다. setTimeout함수는 함수의 참조를 받는다.
함수를 실행한다면 sayHello함수의 반환값은 undefined가 되어 스케쥴이 되지 않는다.
setTimeout을 호출하면 반환 값으로 우리가 실행한 setTimeout의 timerId를 반환한다.
스케쥴을 취소하기 위해서는 clearTimeout함수를 사용한다.
clearTimeout 기본 문법let timerId = setTimeout(...);
clearTimeout(timerId);timerId를 clearTimeout에 넘겨줌으로 써 스케쥴을 취소할 수 있다.
clearTimeout 예시아래의 코드를 실행시키면 알 수 있듯이 브라우저에서의 timerId는 Number다.
node.js에서는 추가적인 메소드를 제공하고 timer object를 반환한다.
let timerId = setTimeout(() => alert('Nothing happens...'), 1000)
alert(timerId)
clearTimeout(timerId)
alert(timerId)1000ms뒤에 실행시키기로 했던 스케쥴이 clearTimeout을 통해 취소된 것을 확인할 수 있다.
2번째 줄의 alert으로 확인한 timerId와 clearTimeout후에 확인한 timerId가 동일하다.
스케쥴을 취소하더라도 timerId가 null이 되지는 않는다.
setInterval함수는 setTimeout과 동일한 문법을 갖는다.
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...);모든 인자들은 setTimeout과 동일한 의미를 갖는다.
다른점은 setTimeout함수는 인자로 받은 함수를 한 번만 실행한다.
setInterval함수는 delay를 주기로 인자로 받은 함수를 계속 실행한다.
스케쥴을 중지하고 싶다면 clearInterval함수를 호출해야한다.
let timerId = setInterval(() => console.log('tick'), 2000)
setTimeout(() => {
clearInterval(timerId)
console.log('stop')
}, 5000)위의 예제는 2s를 주기로 tick이라는 문자열에 콘솔에 출력되게 된다.
setTimeout을 이용해 5s뒤에 tick을 출력하는 스케쥴을 중지하고 stop을 출력하게 된다.
위와 같이 2초 주기로 tick이 출력되었고 5초뒤에 stop이 출력되는 것을 볼 수 있다.
주기적으로 무언가를 실행시키는 방법은 setInterval외에도 재귀적으로 setTimeout을 사용할 수 있다.
아래와 같이 재귀적으로 setTimeout을 사용할 수 있다.
let timerId = setTimeout(function tick() {
console.log('tick')
timerId = setTimeout(tick, 2000)
}, 2000)위의 setTimeout은 현재 실행중인 것이 종료되면 3번째 줄을 스케쥴한다.
재귀적인 setTimeout은 setInterval보다 유연하다.
예를 들어 서버에 5초마다 데이터를 요청하는 프로그램을 작성하고자 한다고 가정해보자.
서버에 요청이 너무 많을 경우 계속 요청을 보내기 보다는 주기를 늘리는 것이 바람직할 것 이다.
아래와 같이 재귀적인 setTimeout을 사용해 코드를 작성할 수 있을 것 이다.
let delay = 5000;
let timerId = setTimeout(function sendRequest() {
... Send Request ...
if (to many request) {
delay *= 2;
}
timerId = setTimeout(request, delay);
}, delay);setInterval은 설정한 delay를 변경할 수 없지만 setTimeout을 재귀적으로 사용하면 가능하다.
또한 재귀적인 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(i)를 100ms마다 실행한다.
우리가 원하는 함수의 실행주기는 100ms지만 실제 delay는 더 적다.
func함수가 실행되며 소비되는 시간이 원인이다.
여기에서 우리가 작성한 func함수가 100ms보다 더 걸리면 다음 함수가 바로 실행될 것이다.
재귀적인 setTimeout을 사용할 경우 고정된 delay를 보장할 수 있다.
새로운 함수의 호출이 이전에 호출된 함수의 끝에 추가되기 때문이다.
setTimeout(func, 0)이나 setTimeout(func)과 같은 코드는 특별하게 사용된다.
위의 코드는 func함수의 실행을 가능한 빠르게 스케쥴링 한다.
하지만 스케쥴러는 현재 진행중인 코드가 모두 끝난뒤에 이 func함수를 호출한다.
이러한 함수 호출을 비동기적으로 실행된다고 한다.
setTimeout(() => console.log('World!'))
console.log('Hello')위의 코드의 작동을 확인하면 Hello가 먼저 출력이된 후 World!가 출력이 된다.
이는 Javscript의 호출 스택과 메시지 큐, 이벤트 루프와 연관이 있다.
World를 출력하는 함수는 0ms의 딜레이를 가져 바로 메시지 큐에 담기게 된다.
이후 Hello를 출력하는 함수가 호출 스택에 추가되고 이 함수가 호출이 먼저 된다.
그 후 이벤트 루프가 호출 스택이 비어있는 것을 확인해 메시지 큐에 담긴 함수를 호출 스택에 추가한다.
그 후 World를 출력하는 함수가 실행되게 된다.
자바스크립트에서 애니메이션을 구현하기 위한 방법으로는 new Date()를 사용한 타이머를 이용한다.
시작 시점과 종료 시점을 변수에 저장해 반복으로 실행하는 방법이다.
이러한 방법은 호출 스택이 지나치게 많다는 단점이 존재한다.
이러한 경우 필요한 함수가 requestAnimationFrame()함수다.
requstAnimationFrame(func)requestAnimationFrame함수는 반복할 함수를 인자로 받는다.
requestAnimationFrame함수 사용의 장점은 아래와 같다.
!(function() {
let start = new Date().getTime()
let i = 1
let callback = function() {
let ts = new Date().getTime()
if (ts - 1000 > start) {
// console.log('End');
} else {
console.log(i++, ts)
requestAnimationFrame(callback)
}
}
requestAnimationFrame(callback)
})()위의 코드를 실행시켜보면 1초동안 callback함수가 60번으로 제한되어 실행된다.
호출 스택이 과도하게 커지는 현상을 방지하며 코드를 작성할 수 있게 된다.
requstAnimationFrame함수를 취소하기 위해서는 cancelAnimationFrame함수를 사용한다.
setTimeout과 setInterval함수와 동일하게 사용할 수 있다.
let requestId = requestAnimationFrame(() => console.log('Hello World!'))
cancelAnimationFrame(requestId)requestAnimationFrame함수를 호출하였지만 바로 cancelAnimationFrame함수가 사용되었다.
따라서 콘솔에는 아무것도 출력되지 않고 코드가 종료되게 된다.