[JS] 디바운스, 쓰로틀 (Debounce, Throttle) with lodash
디바운스(debounce)
연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것
왜쓸까?
검색어 자동완성기능을 구현하는 경우를 생각해보자.
ㄷ 디 딥 디바 디방 디바우 디바운 디바운ㅅ 디바운스
'디바운스' 검색하는데 쓸데없이 api 호출 9번하면 자원낭비 넘 심하고요?
이 때 호출되는 이벤트 콜백 다 실행하지말고 마지막에 호출한거만 실행하는게 디바운스임.
사용예시
ajax요청, 리사이즈 등...
구현
const debounce = (callback, delay) => {
let timer;
return event => {
if (tiemr) clearTimeout(timer);
timer = setTimeout(callback, delay, event);
};
};
스로틀(throttel)
마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것
왜쓸까?
무한스크롤링 구현을 위해 스크롤 이벤트를 받는 경우를 생각해보자.
이거 스크롤 1px마다 스크롤 이벤트 콜백 다 수행하면 브라우저 과로사함..
브라우저는 다른 할일도 많으니까 이런 이벤트 콜백 수행은 일정시간마다 한번씩만 하게 해주는게 쓰로틀임.
사용예시
무한 스크롤링, 지도 이동 등...
구현
function throttle(callback, delay) {
let timer
return event => {
if (timer) return;
timer = setTimeout(() => {
callback(event);
timer = null;
}, delay, event)
}
}
lodash의 debounce, throttle
JavaScript deep dive책에서 위의 코드는 허접이니 더 심화기능 쓰고싶으면 lodash
라이브러리꺼 쓰라고 함
그래서 찾아보니 option을 줘서 기능을 디테일하게 조절하여 사용할 수 있음
_.debounce
_.debounce(func, [wait=0], [options={}])
- options
[options.leading=false] : 이게 true면 첫 발생 이벤트는 일단 실행함
[options.maxWait] : 함수 호출이 딜레이 될 수 있는 maximum 시간.
[options.trailing=true] : 이게 true면 마지막 이벤트 발생 후 wait만큼 지난 후 함수 실행함
options.maxWait 설정하면 debounce + throttle 합친 것처럼 동작함. wait 만큼 debounce 딜레이 적용되고, maxWait만큼 이벤트 최대 호출주기 적용되는거임
그래서 throttle 코드보면 wait == maxWait
인 debounce 함수로 구현되어있음
- 코드 원문 (고봉밥주의)
function debounce(func, wait, options) {
var lastArgs,
lastThis,
maxWait,
result,
timerId,
lastCallTime,
lastInvokeTime = 0,
leading = false,
maxing = false,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
wait = toNumber(wait) || 0;
if (isObject(options)) {
leading = !!options.leading;
maxing = 'maxWait' in options;
maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
function invokeFunc(time) {
var args = lastArgs,
thisArg = lastThis;
lastArgs = lastThis = undefined;
lastInvokeTime = time;
result = func.apply(thisArg, args);
return result;
}
function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time;
// Start the timer for the trailing edge.
timerId = setTimeout(timerExpired, wait);
// Invoke the leading edge.
return leading ? invokeFunc(time) : result;
}
function remainingWait(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime,
timeWaiting = wait - timeSinceLastCall;
return maxing
? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting;
}
function shouldInvoke(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime;
// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
}
function timerExpired() {
var time = now();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// Restart the timer.
timerId = setTimeout(timerExpired, remainingWait(time));
}
function trailingEdge(time) {
timerId = undefined;
// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
function cancel() {
if (timerId !== undefined) {
clearTimeout(timerId);
}
lastInvokeTime = 0;
lastArgs = lastCallTime = lastThis = timerId = undefined;
}
function flush() {
return timerId === undefined ? result : trailingEdge(now());
}
function debounced() {
var time = now(),
isInvoking = shouldInvoke(time);
lastArgs = arguments;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
if (maxing) {
// Handle invocations in a tight loop.
clearTimeout(timerId);
timerId = setTimeout(timerExpired, wait);
return invokeFunc(lastCallTime);
}
}
if (timerId === undefined) {
timerId = setTimeout(timerExpired, wait);
}
return result;
}
debounced.cancel = cancel;
debounced.flush = flush;
return debounced;
}
_.throttle
_.throttle(func, [wait=0], [options={}])
-
options
[options.leading=false] : 이게 true면 첫 발생 이벤트는 일단 실행함
[options.trailing=true] : 이게 true면 마지막 이벤트 발생 후 wait만큼 지난 후 함수 실행함 -
코드 원문
function throttle(func, wait, options) {
var leading = true,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
if (isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
return debounce(func, wait, {
'leading': leading,
'maxWait': wait,
'trailing': trailing
});
}
요약
- 연속적으로 일어날 가능성이 있는 이벤트의 실행 횟수를 줄여 성능적으로 개선하기 위해서 디바운스, 쓰로틀 기법을 사용한다.
- 디바운스는 연속된 이벤트 호출중에 마지막 호출만 실행한다.
- 쓰로틀은 연속된 이벤트를 일정시간마다 한번씩 실행한다.
참고링크
https://lodash.com/docs/4.17.15
Author And Source
이 문제에 관하여([JS] 디바운스, 쓰로틀 (Debounce, Throttle) with lodash), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@say_ye/JS-디바운스-쓰로틀-Debounce-Throttle-with-lodash저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)