📢 어렵고 정석적인 개념 설명보다는 저같은 초보자도 이해하기 쉽게 정리하는 것을 원칙으로 포스팅하고 있습니다. 😄
쓰로틀링과 디바운싱이 뭘까?
쓰로틀링과 디바운싱, 처음 들어보면 굉장히 낯선 단어일 수밖에 없다. 쓰로틀링과 디바운싱을 모르더라도 우리는 코딩 중에 이러한 개념을 이미 코드에 적용하고 있을 수도 있다. 특히, addEventListener를 사용했다면 말이다.
쓰로틀링과 디바운싱은 이벤트 핸들링에서 이벤트의 빈도를 제어하고, 너무 자주 발생하는 이벤트를 제한하는 목적으로 사용된다. 예를 들어서, 특정 함수를 실행하는 클릭 이벤트를 하나 구현했다고 해보자. 쓰로틀링과 디바운싱을 적용하지 않은 클릭 이벤트는 그냥 클릭할 때마다 특정 함수를 계속 실행하기만 할 것이다. 그런데, 클릭할 때마다 특정 함수를 실행하도록 만든 건데 이게 과연 잘못됐다고 할 수 있을까? (잘못됐다고 할 수 있다.)
만약, 세계에서 클릭을 가장 빠르게 하는 사람이 클릭을 엄청 빠른 속도로 계속한다면 어떻게 될까? 클릭 이벤트가 실행될 때마다 특정 함수가 실행될 것이고, 실행된 함수는 브라우저에 계속 쌓이게 되면서 결국 브라우저 성능에 부하를 주게 될 것이다.
쓰로틀링과 디바운싱은 이처럼 이벤트의 무한 호출을 방지하고자 이벤트 간 딜레이를 줘서 그 딜레이 시간 동안에는 아무리 이벤트가 호출되어도 해당 이벤트가 실행되지 않도록 도와준다.
쓰로틀링(throttling) : 일단 1번 호출하고 n초 동안 무시하기
const button = document.getElementById('button');
button.addEventListener('click', throttle);
function throttle() {
let timer = true;
return () => {
if (timer) {
console.log('click!'); // 실행할 동작
timer = false;
setTimeout(() => {
timer = true;
}, 3000);
}
}
}
쓰로틀링이란, 이벤트 호출이 수 만 번 쭉 있을 때 가장 첫 번째 이벤트 호출만 실행하고 나머지 이벤트 호출은 n초 동안 무시하는 것을 말한다. 즉, 처음 먼저 실행해 버리고 딜레이 시간을 갖다가 그 시간이 끝나면 다시 실행하고 또 딜레이를 갖고.. 이런 식이다.
디바운싱(debouncing) : n초 동안 호출이 없을 시 이벤트 실행하기
const button = document.getElementById('button');
button.addEventListener('click', debounce);
function debounce() {
let timer = 0;
return () => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
console.log('click!'); // 실행할 동작
}, 3000);
}
}
쓰로틀링이 처음 이벤트 호출을 기준으로 n초 동안 무시하는 거였다면, 디바운싱은 반대로 마지막 이벤트 호출을 기준으로 n초 동안 아무런 호출이 없었을 때 이벤트가 실행되는 것을 말한다.
동작 원리 : 함수가 함수를 반환하는 클로저
위의 쓰로틀링과 디바운싱 예제를 보면 모두 함수가 함수를 반환하는 형태로 되어 있다. 즉, 외부 함수와 내부 함수가 존재하며 이것을 우리는 클로저(Closure)라고 부르기도 한다. 클로저의 대표적인 특징은 외부 함수에서 선언된 변수를 내부 함수에서 사용할 수 있다는 점이다.
실제로 코드를 보면 timer라는 변수가 모두 외부 함수에서 선언되어 있고, 이 변수를 모두 내부 함수에서 사용하고 있다.
쓰로틀링 예시 코드를 한번 확인해 보자. addEventListener('click', throttle()); 이라고 되어 있다. 우선 클릭과 무관하게 throttle()이라는 함수가 최초로 1번 실행되고, timer라는 변수가 throttle 함수 스코프에 등록된다. (외부 함수가 실행)
그러고 나서, 버튼을 클릭하면 timer 변수가 다시 재선언되는 것이 아니라 return 값인 내부 함수가 실행된다. 즉, 클릭 이벤트는 내부 함수를 실행하는 것이고 내부 함수에 필요한 재료인 timer라는 변수는 매번 내부 함수가 실행될 때마다 재선언할 수는 없으니 외부 함수로 빼서 최초 1번만 실행되도록 한 것이다. (클로저의 특징을 이용한 것이라고 할 수 있다.)
// 클로저
function outer() {
let abc = 1;
console.log(abc);
return () => {
console.log('inner Function');
}
}
let xyz = outer(); // abc
xyz(); // inner Function
함수를 반환하는 함수가 있을 때, 즉 클로저가 있을 때 우리가 가장 헷갈리는 점은 바로 outer()가 실행되면 함수 안에 있는 코드가 전부 실행되는지의 여부이다. 하지만, 클로저는 외부 함수와 내부 함수의 실행 조건이 서로 다르다.
위의 예시처럼 console.log(abc)를 실행하고 싶다면, 그냥 변수에 outer()를 선언하는 것만 해도 실행된다. 하지만, 내부 함수의 console.log('inner Function')을 실행하고 싶다면, outer()를 담은 변수를 함수처럼 끝에 ()를 붙여서 실행해줘야 한다.
이것이 우리가 이벤트리스너를 사용할 때 addEventListener('click', () => throttle())이 아닌 addEventListener('click', throttle())이라고 한 이유이다. 전자는 콜백 함수로 throttle()을 실행했지만, 후자는 콜백 함수 없이 바로 실행되도록 해주었다. 후자로 사용해야지만 외부 함수의 변수를 스코프에 등록시킬 수 있다. (내부 함수에서 외부 함수의 변수를 사용할 수 있다는 말과 같다.)
이와 같은 클로저의 원리를 이해한 다음 다시 쓰로틀링과 디바운싱의 예제 코드를 보면 왜 이렇게 사용했는지 이해할 수 있을 것이다. 쓰로틀링과 디바운싱은 특정 시간만큼 딜레이를 가지는 데 어디서부터 딜레이를 셀 것인지에 대한 기준이 필요했다. 따라서 기준이 될만한 timer라는 변수가 필요했고 이 변수는 최초 한 번만 선언되면 됐기에 클로저를 사용한 것이다.
참고로, 쓰로틀링과 디바운싱은 위처럼 단순히 변수를 true/false로 나눠서 제어하는 게 아닌 Date()를 이용하여 현재 시간과 이벤트 호출 시간을 비교해서 동작하는 것이 정석적인 코드로 알고 있다. 하지만, 이 글은 코드에 중점을 둔 게 아닌 개념에 대한 이해가 우선으로 작성되었기 때문에 코드의 경우 정석적인 코드와는 차이가 있을 수도 있다. (그래도 쓰로틀링과 디바운싱의 역할을 한다는 것은 같다.)
1. 쓰로틀링 : 일단 실행하고 바로 닫아버림. 그리고 n초 후 다시 실행하고 또 닫아버림.
2. 디바운싱 : 일단 닫고 이벤트 호출이 더 이상 없을 때까지 기다리다가 마지막 호출을 기준으로 n초 후 실행.
3. 쓰로틀링은 일단 실행하고 기다리기, 디바운싱은 기다리다가 잠잠해지면 실행하는 게 차이점.
4. 둘의 코드 원리는 클로저를 사용했다는 점. 외부 함수의 변수를 내부 함수에서 사용했음.
'JavaScript' 카테고리의 다른 글
[Javascript] ES6의 이터러블 객체와 이터레이터 객체 간단 정리 (0) | 2024.08.19 |
---|---|
[JavaScript] 객체 지향 프로그래밍을 위한 자바스크립트 클래스 문법 (0) | 2024.05.19 |
[JavaScript] 여러 개의 Promise 객체를 묶어주는 Promise.all (0) | 2023.07.04 |
[JavaScript] Intersection Observer API로 무한 스크롤 구현하기 (0) | 2023.06.30 |
[JavaScript] [] == ![]은 왜 true가 나오는걸까? (0) | 2023.06.02 |