우리는 지금 당장이 아니라 나중에 특정 시간에 기능을 실행하기로 결정할 수도 있습니다. 이를 "통화 예약"이라고 합니다.
이를 위한 두 가지 방법이 있습니다:
setTimeout
사용하면 일정 시간 후에 함수를 한 번 실행할 수 있습니다.
setInterval
사용하면 특정 시간 간격 이후에 시작하여 해당 간격으로 계속해서 반복하여 함수를 반복적으로 실행할 수 있습니다.
이러한 메소드는 JavaScript 사양의 일부가 아닙니다. 그러나 대부분의 환경에는 내부 스케줄러가 있으며 이러한 방법을 제공합니다. 특히 모든 브라우저와 Node.js에서 지원됩니다.
구문:
타이머Id = setTimeout(func|code, [delay], [arg1], [arg2], ...)
매개변수:
func|code
실행할 함수 또는 코드 문자열입니다. 일반적으로 그것은 기능입니다. 역사적인 이유로 코드 문자열을 전달할 수 있지만 권장되지는 않습니다.
delay
실행 전 지연 시간(밀리초)(1000ms = 1초), 기본적으로 0입니다.
arg1
, arg2
…
함수에 대한 인수
예를 들어, 다음 코드는 1초 후에 sayHi()
호출합니다.
함수 sayHi() { 경보('안녕하세요'); } setTimeout(sayHi, 1000);
인수 포함:
function sayHi(문구, 누구) { 경고(문구 + ', ' + 누구 ); } setTimeout(sayHi, 1000, "안녕하세요", "John"); //안녕하세요, 존
첫 번째 인수가 문자열이면 JavaScript는 여기에서 함수를 생성합니다.
따라서 이것은 또한 작동합니다:
setTimeout("alert('안녕하세요')", 1000);
하지만 문자열을 사용하는 것은 권장되지 않습니다. 대신 다음과 같이 화살표 함수를 사용하세요.
setTimeout(() => Alert('안녕하세요'), 1000);
함수를 전달하지만 실행하지는 마세요.
초보 개발자는 함수 뒤에 대괄호 ()
추가하여 실수를 저지르는 경우가 있습니다.
// 잘못된! setTimeout(sayHi(), 1000);
setTimeout
함수에 대한 참조를 기대하기 때문에 작동하지 않습니다. 그리고 여기에서 sayHi()
함수를 실행하고, 그 실행 결과가 setTimeout
에 전달됩니다. 우리의 경우 sayHi()
의 결과는 undefined
으므로(함수는 아무것도 반환하지 않음) 아무 것도 예약되지 않습니다.
setTimeout
호출하면 실행을 취소하는 데 사용할 수 있는 "타이머 식별자" timerId
반환됩니다.
취소할 구문:
let timeId = setTimeout(...); ClearTimeout(timerId);
아래 코드에서는 함수를 예약한 다음 취소합니다(마음이 바뀌었습니다). 결과적으로 아무 일도 일어나지 않습니다.
let timeId = setTimeout(() => Alert("절대로 발생하지 않음"), 1000); 경고(timerId); // 타이머 식별자 ClearTimeout(timerId); 경고(timerId); // 동일한 식별자(취소 후에도 null이 되지 않음)
alert
출력에서 볼 수 있듯이 브라우저에서 타이머 식별자는 숫자입니다. 다른 환경에서는 이것이 다른 것일 수 있습니다. 예를 들어 Node.js는 추가 메서드가 포함된 타이머 개체를 반환합니다.
다시 말하지만, 이러한 방법에 대한 보편적인 사양은 없으므로 괜찮습니다.
브라우저의 경우 타이머는 HTML Living Standard의 타이머 섹션에 설명되어 있습니다.
setInterval
메소드는 setTimeout
과 동일한 구문을 갖습니다.
타이머Id = setInterval(func|code, [delay], [arg1], [arg2], ...)
모든 인수는 동일한 의미를 갖습니다. 그러나 setTimeout
과 달리 함수를 한 번만 실행하는 것이 아니라 지정된 시간 간격 후에 정기적으로 실행합니다.
추가 호출을 중지하려면 clearInterval(timerId)
호출해야 합니다.
다음 예에서는 2초마다 메시지를 표시합니다. 5초 후에 출력이 중지됩니다.
// 2초 간격으로 반복 let timeId = setInterval(() => Alert('tick'), 2000); //5초 후 정지 setTimeout(() => {clearInterval(timerId);alert('stop'); }, 5000);
alert
표시되는 동안 시간이 계속됩니다.
Chrome 및 Firefox를 포함한 대부분의 브라우저에서 내부 타이머는 alert/confirm/prompt
표시하는 동안 계속 "똑딱거림"을 표시합니다.
따라서 위의 코드를 실행하고 한동안 alert
창을 닫지 않으면 다음 alert
실행되는 즉시 표시됩니다. 경고 간의 실제 간격은 2초보다 짧습니다.
무언가를 정기적으로 실행하는 방법에는 두 가지가 있습니다.
하나는 setInterval
입니다. 다른 하나는 다음과 같이 중첩된 setTimeout
입니다.
/** 대신: let timeId = setInterval(() => Alert('tick'), 2000); */ let 타이머Id = setTimeout(function Tick() { 경고('틱'); timeId = setTimeout(tick, 2000); // (*) }, 2000);
위의 setTimeout
현재 호출이 끝나면 바로 다음 호출을 예약합니다 (*)
.
중첩된 setTimeout
은 setInterval
보다 더 유연한 방법입니다. 이렇게 하면 현재 통화의 결과에 따라 다음 통화가 다르게 예약될 수 있습니다.
예를 들어, 5초마다 서버에 데이터를 요청하는 요청을 보내는 서비스를 작성해야 하는데, 서버에 과부하가 걸릴 경우 간격을 10, 20, 40초로 늘려야 합니다.
의사코드는 다음과 같습니다.
지연 = 5000으로 설정합니다. 타이머Id = setTimeout(함수 요청() { ...요청 보내기... if (서버 과부하로 인해 요청이 실패했습니다) { // 다음 실행 간격을 늘립니다. 지연 *= 2; } timeId = setTimeout(요청, 지연); }, 지연);
그리고 우리가 예약하는 기능이 CPU를 많이 사용한다면 실행에 소요되는 시간을 측정하고 조만간 다음 호출을 계획할 수 있습니다.
중첩된 setTimeout
사용하면 setInterval
보다 더 정확하게 실행 간의 지연을 설정할 수 있습니다.
두 개의 코드 조각을 비교해 보겠습니다. 첫 번째는 setInterval
사용합니다.
내가 = 1이라고 하자; setInterval(함수() { func(i++); }, 100);
두 번째는 중첩된 setTimeout
사용합니다.
내가 = 1이라고 하자; setTimeout(함수 실행() { func(i++); setTimeout(실행, 100); }, 100);
setInterval
의 경우 내부 스케줄러는 100ms마다 func(i++)
실행합니다.
눈치채셨나요?
setInterval
에 대한 func
호출 간의 실제 지연은 코드보다 적습니다!
func
의 실행에 걸리는 시간이 간격의 일부를 "소모"하기 때문에 이는 정상적인 현상입니다.
func
의 실행이 예상보다 길어지고 100ms 이상 걸릴 수도 있습니다.
이 경우 엔진은 func
완료될 때까지 기다린 다음 스케줄러를 확인하고 시간이 다 되면 즉시 다시 실행합니다.
극단적인 경우, 함수가 항상 delay
(ms)보다 오래 실행되면 호출은 전혀 일시 중지되지 않고 발생합니다.
다음은 중첩된 setTimeout
에 대한 그림입니다.
중첩된 setTimeout
고정 지연(여기서는 100ms)을 보장합니다.
이전 통화가 끝나면 새로운 통화가 계획되어 있기 때문입니다.
가비지 수집 및 setInterval/setTimeout 콜백
setInterval/setTimeout
에 함수가 전달되면 해당 함수에 대한 내부 참조가 생성되고 스케줄러에 저장됩니다. 이는 함수에 대한 다른 참조가 없더라도 함수가 가비지 수집되는 것을 방지합니다.
// 함수는 스케줄러가 호출할 때까지 메모리에 유지됩니다. setTimeout(function() {...}, 100);
setInterval
의 경우 함수는 clearInterval
이 호출될 때까지 메모리에 유지됩니다.
부작용이 있습니다. 함수는 외부 어휘 환경을 참조하므로 함수가 작동하는 동안 외부 변수도 작동합니다. 함수 자체보다 훨씬 더 많은 메모리를 차지할 수 있습니다. 따라서 예약된 기능이 더 이상 필요하지 않으면 아주 작더라도 취소하는 것이 좋습니다.
특별한 사용 사례가 있습니다: setTimeout(func, 0)
또는 그냥 setTimeout(func)
.
이는 가능한 한 빨리 func
실행을 예약합니다. 그러나 스케줄러는 현재 실행 중인 스크립트가 완료된 후에만 이를 호출합니다.
따라서 함수는 현재 스크립트 "직후"에 실행되도록 예약됩니다.
예를 들어, 다음은 "Hello"를 출력한 다음 즉시 "World"를 출력합니다.
setTimeout(() => Alert("세계")); Alert("안녕하세요");
첫 번째 줄은 "0ms 후에 통화를 달력에 넣습니다". 그러나 스케줄러는 현재 스크립트가 완료된 후에만 "캘린더를 확인"하므로 "Hello"
가 첫 번째이고 "World"
그 뒤입니다.
제로 지연 시간 초과의 고급 브라우저 관련 사용 사례도 있는데, 이에 대해서는 이벤트 루프: 마이크로태스크 및 매크로태스크 장에서 논의하겠습니다.
제로 지연은 실제로 0이 아닙니다(브라우저에서).
브라우저에는 중첩된 타이머가 실행될 수 있는 빈도에 제한이 있습니다. HTML Living Standard에서는 "다섯 개의 중첩된 타이머 이후 간격은 강제로 최소 4밀리초가 되어야 합니다."라고 말합니다.
아래 예를 통해 이것이 무엇을 의미하는지 보여드리겠습니다. setTimeout
호출은 지연 없이 자체 일정을 다시 설정합니다. 각 호출은 times
배열에서 이전 호출의 실시간 시간을 기억합니다. 실제 지연은 어떤 모습인가요? 보자:
시작하자 = Date.now(); 시간 = []; setTimeout(함수 실행() { times.push(Date.now() - 시작); // 이전 호출의 지연을 기억합니다. if (start + 100 < Date.now()) Alert(times); // 100ms 이후의 지연을 표시합니다. 그렇지 않으면 setTimeout(실행); //그렇지 않으면 일정을 다시 잡습니다 }); // 출력 예: // 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
첫 번째 타이머는 즉시 실행되고(스펙에 명시된 대로) 9, 15, 20, 24...
표시됩니다. 호출 사이에 4ms 이상의 의무적 지연이 적용됩니다.
setTimeout
대신 setInterval
사용하면 비슷한 일이 발생합니다. setInterval(f)
지연 없이 f
몇 번 실행한 다음 4ms 이상의 지연으로 실행합니다.
이러한 제한은 고대부터 발생했으며 많은 스크립트가 이에 의존하므로 역사적인 이유로 존재합니다.
서버 측 JavaScript의 경우 이러한 제한이 존재하지 않으며 Node.js의 setImmediate와 같이 즉시 비동기 작업을 예약하는 다른 방법이 있습니다. 따라서 이 메모는 브라우저별로 다릅니다.
setTimeout(func, delay, ...args)
및 setInterval(func, delay, ...args)
메소드를 사용하면 delay
밀리초 후에 func
한 번/정기적으로 실행할 수 있습니다.
실행을 취소하려면 setTimeout/setInterval
이 반환한 값으로 clearTimeout/clearInterval
호출해야 합니다.
중첩된 setTimeout
호출은 setInterval
보다 더 유연한 대안이므로 실행 사이의 시간을 더 정확하게 설정할 수 있습니다.
setTimeout(func, 0)
( setTimeout(func)
과 동일)을 사용한 지연 없는 예약은 "가능한 한 빨리, 그러나 현재 스크립트가 완료된 후에" 호출을 예약하는 데 사용됩니다.
브라우저는 5개 이상의 중첩된 setTimeout
호출 또는 setInterval
(5번째 호출 후)에 대한 최소 지연을 4ms로 제한합니다. 그것은 역사적인 이유 때문입니다.
모든 예약 방법이 정확한 지연을 보장하는 것은 아닙니다.
예를 들어, 브라우저 내 타이머는 여러 가지 이유로 느려질 수 있습니다.
CPU가 과부하되었습니다.
브라우저 탭이 백그라운드 모드에 있습니다.
노트북이 배터리 절약 모드에 있습니다.
브라우저 및 OS 수준 성능 설정에 따라 최소 타이머 해상도(최소 지연)를 300ms 또는 심지어 1000ms까지 늘릴 수 있습니다.
중요도: 5
from
에서 시작하여 to
로 끝나는 숫자를 매초마다 출력하는 함수 printNumbers(from, to)
작성하세요.
솔루션의 두 가지 변형을 만듭니다.
setInterval
사용.
중첩된 setTimeout
사용.
setInterval
사용 :
함수 printNumbers(from, to) { 현재 = from; 타이머Id = setInterval(function() { 경고(현재); if (현재 == to) { ClearInterval(timerId); } 현재++; }, 1000); } // 용법: printNumbers(5, 10);
중첩된 setTimeout
사용:
함수 printNumbers(from, to) { 현재 = from; setTimeout(함수 go() { 경고(현재); if (현재 < to) { setTimeout(go, 1000); } 현재++; }, 1000); } // 용법: printNumbers(5, 10);
두 솔루션 모두 첫 번째 출력 전에 초기 지연이 있습니다. 이 함수는 처음 1000ms
후에 호출됩니다.
함수가 즉시 실행되도록 하려면 다음과 같이 별도의 줄에 추가 호출을 추가할 수 있습니다.
함수 printNumbers(from, to) { 현재 = from; 함수 이동() { 경고(현재); if (현재 == to) { ClearInterval(timerId); } 현재++; } 가다(); 타이머Id = setInterval(go, 1000); } printNumbers(5, 10);
중요도: 5
아래 코드에는 setTimeout
호출이 예약되어 있으며, 완료하는 데 100ms 이상이 걸리는 대규모 계산이 실행됩니다.
예약된 기능은 언제 실행되나요?
루프 후.
루프 전.
루프의 시작 부분에 있습니다.
alert
어떤 내용이 표시되나요?
내가 = 0이라고 하자; setTimeout(() => 경고(i), 100); // ? // 이 함수를 실행하는 시간이 100ms보다 크다고 가정합니다. for(let j = 0; j < 100000000; j++) { 나++; }
모든 setTimeout
현재 코드가 완료된 후에만 실행됩니다.
i
마지막 것입니다: 100000000
.
내가 = 0이라고 하자; setTimeout(() => 경고(i), 100); // 100000000 // 이 함수를 실행하는 시간이 100ms보다 크다고 가정합니다. for(let j = 0; j < 100000000; j++) { 나++; }