디바운싱(Debouncing)
디바운스란 이벤트 발생 후 일정 시간동안 해당 이벤트가 재 발생하지 않으면 콜백을 호출하는 것을 말합니다.
언제 사용하나요?
자동검색기능을 구현하는 경우, Input의 onchange가 발생할 때 마다 검색 요청을 하는 콜백을 호출하면 너무 빈번하게 요청하게 되어 문제가 됩니다. 이 때, onchange가 발생한 뒤 일정 시간동안 이벤트가 재발생하지 않았다면, 사용자가 해당 키워드로 검색을 원하는 상황으로 간주하고 검색요청을 전송하도록 디바운스를 적용하여 불필요한 API호출을 막을 수 있습니다.
구현은 setTimeout과 클로저를 통해 쉽게 구현할 수 있습니다.
구현
1<div
2 class="card relative shadow-md border border-gray-100 bg-white max-w-[600px] h-[300px] mx-auto top-[40%] translate-y-[-50%] p-5 rounded-md"
3>
4 <div class="wrapper flex flex-col h-full">
5 <div class="flex flex-col gap-1">
6 <label for="search-input">Search</label>
7 <input id="search-input" type="text" class="border border-gray-200 h-8 rounded-md px-3" />
8 </div>
9
10 <div class="grow"></div>
11
12 <div>Search Invocation Result: <span id="display"></span></div>
13 </div>
14</div>
151main();
2
3function main() {
4 const timeout = 500;
5 const searchInput = document.getElementById('search-input');
6 const display = document.getElementById('display');
7
8 // 클로져에서 참조하는 변수. 콜백함수 자신의 스코프 밖에 있지만 클로저는 선언 시점의 스코프 외부 변수를 읽고 쓸 수 있다.
9 let previousTimeoutId = null;
10
11 searchInput.addEventListener('keyup', (e) => {
12 // 타임아웃되지 않았다면 해당 콜백의 호출을 취소한다.
13 if (previousTimeoutId) {
14 clearTimeout(previousTimeoutId);
15 }
16
17 // 클로져에서 참조하는 변수에 새 타임아웃의 아이디를 저장한다.
18 previousTimeoutId = setTimeout(() => {
19 console.log('search!', {
20 keyword: searchInput.value,
21 });
22 display.innerText = searchInput.value;
23 }, timeout);
24 });
25}
26이벤트가 발생하면 setTimeout을 통해 일정시간이 지나면 검색요청을 하도록 구현합니다. 이 때 setTimeout의 반환값을 클로져 변수에 저장합니다. 이후, 일정시간이 지나기 전 onchange 이벤트가 재발생하면 클로져 변수에 담긴 setTimeout id를 통해 clearTimeout을 호출한 뒤 setTimeout을 재호출합니다. 이렇게 하여 일정 시간동안 이벤트가 발생하지 않아야만 콜백이 호출되도록 개선할 수 있습니다.
쓰로틀링(Throttling)
쓰로틀링이란 콜백이 과도하게 호출되지 않도록 제한하는 것을 말합니다.
언제 사용하나요?
pointermove와 같이 짧은 간격으로 자주 발생하는 이벤트에 리스너를 바인딩하는 경우, 쓰로틀링을 걸어서 불필요하게 자주 호출되는 콜백 문제를 방지할 수 있습니다.
구현
1<div
2 id="box"
3 class="w-[300px] h-[300px] mx-auto mt-[50%] translate-y-[-50%] bg-amber-200 shadow-xl border border-gray-200 flex justify-center items-center"
4>
5 <span class="indicator text-2xl text-gray-700 pointer-events-none">0</span>
6</div>
7
8<div id="pointer" class="w-10 h-10 bg-slate-700 shadow-md fixed hidden pointer-events-none"></div>
91main();
2
3function main() {
4 const box = document.getElementById('box');
5 const pointer = document.getElementById('pointer');
6 const indicator = box.querySelector('span.indicator');
7 let counter = 0;
8 let requesting = false;
9
10 box.addEventListener('pointermove', (e) => {
11 const { x, y } = e;
12
13 if (requesting) {
14 return;
15 }
16
17 requesting = true;
18
19 const animate = () => {
20 requesting = false;
21 counter++;
22
23 if (pointer.classList.contains('hidden')) {
24 pointer.classList.remove('hidden');
25 }
26
27 pointer.style.left = x + 'px';
28 pointer.style.top = y + 'px';
29 indicator.innerHTML = counter;
30 };
31
32 requestAnimationFrame(animate);
33 });
34}
35쓰로틀링은 클로져를 통해 구현할 수 있습니다. 리스너를 바인딩하기 전, 클로저 변수를 하나 선언하고, 여기에 이전 콜백이 호출된 시간을 기록합니다. 콜백이 호출되었을 때, 이전 호출 시간으로부터 일정시간 지났는지 여부를 검사합니다. 지났다면 콜백을 호출하고, 지나지 않았다면 콜백을 호출하지 않습니다. 이렇게 하여 쓰로틀링을 구현할 수 있습니다.