함수를 명시적으로 호출하면 함수가 즉시 실행된다.
즉시 실행하지않고 일정시간이 경과된 후 호출하기 위해서는 타이머함수를 사용한다.
이를 호출 스케줄링(scheduling a call)이라고 한다.
타이머를 생성할 수 있는 타이머함수
setTimeout
: 타이머가 만료되면 콜백함수가 단 한번만 호출
setInterval
: 타이머가 만료될 때마다 콜백함수를 반복 호출
타이머를 제거할 수 있는 타이머함수
clearTimeout
clearInterval
자바스크립트엔진은 단 하나의 실행컨텍스트를 갖기 때문에 두가지이상의 테스크를 동시에 실행할 수 없다. 즉 자바스크립트엔진은 싱글스레드로 동작한다.
따라서 타이머함수는 비동기방식으로 동작한다.
setTimeout
setInterval
함수는 생성된 타이머를 식별할 수 있는 고유한 타이머 id를 반환한다.
반환한 타이머 id는 브라우저 환경인경우 숫자이며, Node.js환경인경우 객체다.
각각의 타이머는 clearTimeout
clearInterval
로 취소할 수 있다.
const timerId = setTimeout(function(){
console.log('hello');
},1000);
// hello 단 1번찍힘
clearTimeout(timerId);
const timerId = setInterval(function(){
console.log('hello');
},1000);
// hello hello hello hello ... 1초마다 반복해서 찍힘
clearInterval(timerId);
scroll, resize, input, mousemove 같은 이벤트는 짧은 시간 간격으로 연속해서 발생한다. (scroll시 height가 계속변하는것 등)
디바운스와 스로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 이벤트 핸들러의 호출을 방지하는 프로그래밍기법이다.
일반적인 이벤트핸들러와 디바운스, 스로틀 이벤트핸들러의 호출빈도
<button>click me</button>
<pre>일반 클릭 카운터 <span id='normal-msg'>0</span></pre>
<pre>디바운스 클릭 카운터 <span id='debounce-msg'>0</span></pre>
<pre>스로틀 클릭 카운터 <span id='throttle-msg'>0</span></pre>
<script>
const button = document.querySelector('button');
const normalMsg = document.getElementById('normal-msg');
const debounceMsg = document.getElementById('debounce-msg');
const throttleMsg = document.getElementById('throttle-msg');
const debounce = (callback,delay) => {
let timerId;
return event => {
if (timerId) clearTimeout(timerId);
timerId = setTimeout(callback,delay,event);
};
};
const throttle = (callback,delay) => {
let timerId;
return event => {
if(timerId) return;
timerId = setTimeout(() => {
callback(event);
timerId = null;
},delay,event);
};
};
button.addEventListener('click',() => {
normalMsg.textContent = +normalMsg.textContent + 1;
});
button.addEventListener('click',debounce(() => {
debounceMsg.textContent = +debounceMsg.textContent + 1;
},500));
button.addEventListener('click',throttle(() => {
throttleMsg.textContent = + throttleMsg.textContent + 1;
},500));
</script>
디바운스(debounce)는 짧은 시간 간격으로 이벤트가 연속해서 발생하면 이벤트핸들러를 호출하지 않다가 일정시간이 경과한 이후에 이벤트 핸들러가 한번만 호출되도록 한다.
즉 이벤트를 그룹화해서 마지막에 한번만 이벤트핸들러가 호출된다.
resize
이벤트를 발생시킬 때 많이 사용하는것같다.
실무에서는 Lodash의 debounce
함수를 사용하는것을 권장한다.
let timer;
$(window).resize(function() {
clearTimeout(timer);
timer = setTimeout(function() {
// 실행할 코드 내용
},2000)
});
이벤트가 발생할 때마다 이전에 설정해둔 내용을 clear시키고 다시 새로 timer를 설정해준다.
2초내에 이벤트가 발생하지 않으면 이벤트가 끝난것으로 생각해서 적어둔 코드내용이 실행된다.
스로틀(throttle)은 짧은 시간 간격으로 이벤트가 연속해서 발생하더라도 일정시간간격으로 이벤트핸들러가 최대 한번만 호출되도록 한다.
즉 짧은 시간간격으로 연속해서 발생하더라도 일정 시간 간격으로 이벤트핸들러가 호출되도록 호출주기를 만든다.
scroll
이벤트를 발생시킬 때 많이 사용하는것같다.
실무에서는 Lodash의 throttle
함수를 사용하는것을 권장한다.
둘 다 이벤트를 그룹화해서 연속해서 발생하는 이벤트의 과도한 이벤트핸들러의 호출을 방지한다.
디바운싱은 이벤트가 연속적으로 계속 발생하더라도 모두 무시하고 설정한 특정 시간동안 이벤트가 발생하지 않았을 때 맨 마지막 이벤트를 딱 한번 발생시키지만
스로틀은 설정한 특정 시간 주기로 계속 실행이 된다. 따라서 호출주기에 따라 실행 시키고 싶은 코드 내용이 실행이 되긴 한다.
debounce,throttle도 어쨌든 delay 하기때문에 완벽하진 않다.
그래서 startTransition등을 쓰지만 이런기법이 있다 정도만 알면된다.
다음은 Lodash의 debounce
와 throttle
을 쓴 함수이다.
<input id="input" />
<p id="p"></p>
<script>
import { throttle, debounce } from "lodash";
const input = document.getElementById("input");
const p = document.getElementById("p");
const handleChangeInput = (e) => {
console.log("input!");
p.textContent = e.target.value;
};
input.addEventListener("input", throttle(handleChangeInput, 1000));
// input.addEventListener("input", debounce(handleChangeInput, 1000));
</script>