节流防抖
在前端开发中,经常会遇到高频操作,如:resize、scroll、mousemove、drag、onInput等。但实际上,我们并不需要那么高的频率操作,因此,常见的解决方案有:防抖和节流。
-
节流阀 throttle案例
高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率, 每次触发事件时都判断当前是否有等待执行的延时函数。常规思路,判断延迟函数是否在执行中,是则等待执行完成。
throttle(150, function(){}) // loadsh
// method 1 function throttle(method, context, age1) { // 如果是第一次触发,立刻执行 if (typeof method.tId === "undefined") { method.call(context, age1); } if (method.tId) { return; } method.tId = setTimeout(function() { method.call(context, age1); method.tId = 0; }, 100); } // method 2 function throttle(func, wait, options) { let leading = true let trailing = true if (typeof func != 'function') { throw new TypeError('Expected a function') } 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 }) } // method 3 // @atleast 至少间隔时间 var throttle = function (fn, delay, atleast) { var timer = null; var previous = null; return function () { var now = +new Date(); if ( !previous ) previous = now; if ( now - previous > atleast ) { fn(); // 重置上一次开始时间为本次结束时间 previous = now; } else { clearTimeout(timer); timer = setTimeout(function() { fn(); }, delay); } } }; // throttle(renewToken, 300000, { 'trailing': false }) // method 4 /** * @desc 函数节流 * @param func 函数 * @param wait 延迟执行毫秒数 * @param type 1 表时间戳版,2 表定时器版 */ function throttle(func, wait ,type) { if(type===1){ let previous = 0; }else if(type===2){ let timeout; } return function() { let context = this; let args = arguments; if(type===1){ let now = Date.now(); if (now - previous > wait) { func.apply(context, args); previous = now; } }else if(type===2){ if (!timeout) { timeout = setTimeout(() => { timeout = null; func.apply(context, args) }, wait) } } } }
-
惰性提交, 防抖函数
函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。常规实现思路,每次触发事件时,取消上次调用,并重新计算时间开始下一次。
debounce(150, function(){}) // loadsh
// method 1 function debounce(fn, interval) { var timer = null; function delay() { var target = this; var args = arguments; return setTimeout(function(){ fn.apply(target, args); }, interval); } return function() { if (timer) { clearTimeout(timer); } timer = delay.apply(this, arguments); } }; // method 2 function _debounce (fn, delay, context) { var timer = null if (delay === 0) { return fn } return function () { var eContext = context || this var args = arguments clearTimeout(timer) timer = setTimeout(function () { fn.apply(eContext, args) }, delay) } } // method 3 function debounce(func, wait, options) { let lastArgs, lastThis, maxWait, result, timerId, lastCallTime let lastInvokeTime = 0 let leading = false let maxing = false let trailing = true if (typeof func != 'function') { throw new TypeError('Expected a function') } wait = +wait || 0 if (isObject(options)) { leading = !!options.leading maxing = 'maxWait' in options maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait trailing = 'trailing' in options ? !!options.trailing : trailing } function invokeFunc(time) { const args = lastArgs const 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) { const timeSinceLastCall = time - lastCallTime const timeSinceLastInvoke = time - lastInvokeTime const result = wait - timeSinceLastCall return maxing ? Math.min(result, maxWait - timeSinceLastInvoke) : result } function shouldInvoke(time) { const timeSinceLastCall = time - lastCallTime const 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() { const time = Date.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(Date.now()) } function debounced(...args) { const time = Date.now() const isInvoking = shouldInvoke(time) lastArgs = args lastThis = this lastCallTime = time if (isInvoking) { if (timerId === undefined) { return leadingEdge(lastCallTime) } if (maxing) { // Handle invocations in a tight loop. timerId = setTimeout(timerExpired, wait) return invokeFunc(lastCallTime) } } if (timerId === undefined) { timerId = setTimeout(timerExpired, wait) } return result } debounced.cancel = cancel debounced.flush = flush return debounced } // Invoke `sendMail` when clicked, debouncing subsequent calls. /* jQuery(element).on('click', debounce(sendMail, 300, { * 'leading': true, * 'trailing': false * })) */
-
节流函数(throttle) vs 防抖函数(debounce)
函数防抖:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
区别:函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
通俗地讲,throttle函数每隔delay time执行一次函数,类似于requestAnimationFrame; debounce函数过了delay time执行一次,未到delay time重新计时,并且不执行函数。