오늘 다룰 주제인 쓰로틀과 디바운스는 브라우저에서 이벤트에 등록된 콜백함수의 호출을 제어하기 위한 기법입니다.
보통 resize, scroll, touch와 같은 이벤트들은 짧은 시간동안 수십, 수백개에 이벤트가 발생할 수 있습니다.
이때 만약 콜백함수가 API를 호출하거나 무거운 연산을 수행한다면 이는 그대로 성능 저하의 원인이 되거나 불필요한 비용을 발생시킬 수 있어 필요에 따라 적절히 제어할 필요가 있습니다.
그래서 이 글에서는 두 기법의 차이는 무엇이고 어떻게 작성하는지 알아보겠습니다.
먼저, 쓰로틀은 이벤트를 일정 주기로 발생시키는것을 목적으로 합니다.
대표적으로 사용되는 예는 포털이나 쇼핑몰과 같이 검색이 필요한 사이트에서 유저가 검색을 위해 키보드 이벤트가 발생했을 때 추천검색어를 보여주는 경우입니다.
키보드 이벤트의 경우 짧은 시간에 많이 발생하는 이벤트입니다.
특히, 한글의 경우 자음과 모음을 조합하는 형태로 사용하기 때문에 예를 들어 "언제"라는 단어를 검색한다고 한다면 ㅇ, 어, 언, 얹, 언제
에서 모든 키 입력이 발생하고 ㅇ, 어, 얹
과 같은 단어는 굳이 호출할 필요가 없는 단어입니다.
그래서 이런 경우 쓰로틀을 사용해 키보드 입력 횟수와 상관없이 이벤트가 연속적으로 발생하고 있다면 정해진 일정 시간마다 콜백함수를 호출하여 API의 호출 횟수를 줄일 수 있습니다.
const inputBox = document.querySelector("#inputBox");
// Throttle
let timer;
function throttle(callbackFn, timeout) {
if(!timer) {
timer = setTimeout(() => {
timer = null;
callbackFn();
}, timeout);
}
}
inputBox.addEventListener("keyup", () => {
console.log("event!!");
throttle(() => console.log("API called!"), 500);
});
위 사진에서 콘솔에 찍히는것을 보면 이벤트의 횟수와 상관없이 API called 라는 문자열은 0.5초에 한번씩만 찍히는 모습을 볼 수 있습니다.
디바운스는 일정시간 내에 반복적으로 이벤트가 발생했을 때 이벤트 횟수와 관계없이 마지막 이벤트가 발생한 후 일정시간 뒤에 콜백함수를 한번만 호출하는 기법입니다.
대표적으로 resize 이벤트의 경우 조금만 사이즈를 변경해도 이벤트가 어마어마하게 발생합니다.
만약, 이 이벤트가 발생했을 때마다 무거운 연산이 필요한 로직이 콜백함수에 포함되어 있다면 이는 성능에 무리를 줄 수 있습니다.
그래서 이를 방지하고자 debounce를 사용하게 됩니다.
let timer;
function debounce(callbackFn, timeout) {
if(timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
callbackFn();
}, timeout);
}
window.addEventListener("resize", () => {
console.log("event");
debounce(() => console.log("debounce!"), 200);
});
debounce를 적용해보면 아래 사진과 같이 마지막 이벤트가 발생되고 정해진 일정시간이 지나기전까지 이벤트가 발생하지 않는다면 그 때 콜백함수를 호출하게 됩니다.
여기서 주의해야할 것은 디바운스와 쓰로틀 모두 timer를 이용하는데 이 timer 역할을 하는 변수는 꼭 콜백함수의 외부에서 선언되어야 합니다.
내부에서 선언 될 경우 매 함수 호출시마다 새로운 timer를 변수로 선언하기 때문에 사용하지 않는것과 똑같이 콜백함수를 호출합니다.
{
let timer; // ⭕⭕⭕
function debounce(callbackFn, timeout) {
let timer; // ❌❌❌
if(timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
callbackFn();
}, timeout);
}
}
lodash 라이브러리는 JS의 유틸리티 라이브러리로 객체, 함수, 배열등의 자료형을 위한 메소드 또는 그 외 다양한 함수들을 지원합니다.
다양한 유틸리티 함수들을 사용할 수 있도록 제공해주며 물론 이 라이브러리에는 쓰로틀과 디바운스도 포함되어 있습니다.
lodash의 설치를 위해서는 아래와 같은 명령어들을 사용합니다.
// npm
npm install lodash
// yarn
yarn add lodash
// CDN
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script>
lodash는 기본적으로 _
를 사용하기 때문에 _.function
형태로 사용합니다.
// throttle(func, wait?, options?)
import _ from 'lodash';
inputBox.addEventListener("keyup", () => throttle());
const throttle = _.throttle(() => {
console.log("throttle!");
}, 500);
// debounce(func, wait?, options?)
import _ from 'lodash';
window.addEventListener("resize", () => {
debounceFn();
});
const debounceFn = _.debounce(function() {
console.log("debounce!");
}, 200);
또한 lodash의 쓰로틀과 디바운스는 몇 가지 인자를 추가로 받을 수 있습니다.
func는 콜백함수, wait은 지연시간을 말하며 일정시간 뒤에 콜백함수를 호출합니다.
options의 경우 쓰로틀과 디바운스모두 leading과 trailing이라는 속성을 가지고 있으며 디바운스의 경우 maxWait이라는 속성을 추가로 설정할 수 있습니다.
maxWait (only debounce) : 콜백함수의 호출을 일정시간 지연하는 옵션으로 ms 단위로 입력합니다. 만약, wait과 options의 maxWait이 모두 설정되어 있다면 둘 중 더 긴 시간을 기준으로 딜레이됩니다.
leading : 이벤트가 처음 발생했을 때 콜백함수를 실행시킬지의 여부를 말하며 특히 디바운스의 경우 이벤트가 10초동안 계속 발생하는 상황이라면 콜백함수는 10초가 지난 후에야 실행됩니다. 그래서 이를 기다리지 않고 첫 이벤트가 발생하는 즉시 먼저 콜백함수를 실행시키고 싶다면 이 leading속성의 값을 true로 설정하면 됩니다.
trailing : 마지막 이벤트가 발생했을 때 콜백함수를 호출시킬지에 대한 여부이며 이 값을 false로 지정한 경우 마지막 이벤트에 대한 콜백함수는 호출되지 않게 됩니다. 아래 사진을 보면 더 이해하기 쉽습니다.
출처: ryanpcmcquen.org
위쪽 그림이 trailing이 false일 때, 아래쪽 그림이 trailing이 true일 때 입니다.
디바운스 (Debounce) | 쓰로틀 (Throttle) | |
---|---|---|
목적 | 일정시간내에 발생하는 이벤트에 대해 횟수와 관계없이 한번만 콜백함수를 호출 | 일정시간내에 발생하는 이벤트에 대해 횟수와 관계없이 정해진 시간동안 반복해서 콜백함수를 호출 |
대표 예시 | resize이벤트, 연속 클릭 방지, 지도 어플(표시되는 지역을 옮겨 새로운 정보를 받아올 때) | 무한 스크롤링, 검색어 추천 |
여기서 말하는 예시는 일반적으로 이런 상황에 자주 쓰일 뿐이며 "이런 상황에는 꼭 이걸 사용해야 한다!" 이런것은 아니기 때문에 상황에 맞춰서 사용하시면 되겠습니다.