_.debounce(func, [wait=0], [options={}])
101
_.debounce(func, [wait=0], [options={}])
jQuery(window).on('resize', debounce(calculateLayout, 150))
jQuery(element).on('click', debounce(sendMail, 300, { 'leading': true, 'trailing': false }))
const debounced = debounce(batchLog, 250, { 'maxWait': 1000 }) const source = new EventSource('/stream') jQuery(source).on('message', debounced)
jQuery(window).on('popstate', debounced.cancel)
检查是否调用处于等待状态
const status = debounced.pending() ? "Pending..." : "Ready"
参数
func (Function): 需要被去抖的函数
[wait=0] (number): 推迟执行的毫秒数,如果省略这个参数,requestAnimationFrame会被使用
[options={}] (Object): 配置对象
[options.leading=false] (boolean): 是否在wait时延之前调用
[options.maxWait] (number): 在func被调用之前的最大延迟时间
[options.trailing=true] (boolean): 是否在wait时延过后调用
返回值
(Function): 返回新的被去抖的函数
例子
// Avoid costly calculations while the window size is in flux. jQuery(window).on('resize', _.debounce(calculateLayout, 150)); // Invoke `sendMail` when clicked, debouncing subsequent calls. jQuery(element).on('click', _.debounce(sendMail, 300, { 'leading': true, 'trailing': false })); // Ensure `batchLog` is invoked once after 1 second of debounced calls. var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); var source = new EventSource('/stream'); jQuery(source).on('message', debounced); // Cancel the trailing debounced invocation. jQuery(window).on('popstate', debounced.cancel);
去抖流程:
首次进入函数时因为 lastCallTime === undefined 并且 timerId === undefined,所以会执行 leadingEdge,如果此时 leading 为 true 的话,就会执行 func。同时,这里会设置一个定时器,在等待 wait(s) 后会执行 timerExpired,timerExpired 的主要作用就是触发 trailing。
如果在还未到 wait 的时候就再次调用了函数的话,会更新 lastCallTime,并且因为此时 isInvoking 不满足条件,所以这次什么也不会执行。
时间到达 wait 时,就会执行我们一开始设定的定时器timerExpired,此时因为time-lastCallTime < wait,所以不会执行 trailingEdge。
这时又会新增一个定时器,下一次执行的时间是 remainingWait,这里会根据是否有 maxwait 来作区分:
如果没有 maxwait,定时器的时间是 wait - timeSinceLastCall,保证下一次 trailing 的执行。
如果有 maxing,会比较出下一次 maxing 和下一次 trailing 的最小值,作为下一次函数要执行的时间。
最后,如果不再有函数调用,就会在定时器结束时执行 trailingEdge。
下面是流程图片描述:
节流与去抖的区别
源代码:
import isObject from './isObject.js' import root from './.internal/root.js' /** * Creates a debounced function that delays invoking `func` until after `wait` * milliseconds have elapsed since the last time the debounced function was * invoked, or until the next browser frame is drawn. The debounced function * comes with a `cancel` method to cancel delayed `func` invocations and a * `flush` method to immediately invoke them. Provide `options` to indicate * whether `func` should be invoked on the leading and/or trailing edge of the * `wait` timeout. The `func` is invoked with the last arguments provided to the * debounced function. Subsequent calls to the debounced function return the * result of the last `func` invocation. * * **Note:** If `leading` and `trailing` options are `true`, `func` is * invoked on the trailing edge of the timeout only if the debounced function * is invoked more than once during the `wait` timeout. * * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred * until the next tick, similar to `setTimeout` with a timeout of `0`. * * If `wait` is omitted in an environment with `requestAnimationFrame`, `func` * invocation will be deferred until the next frame is drawn (typically about * 16ms). * * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) * for details over the differences between `debounce` and `throttle`. * * @since 0.1.0 * @category Function * @param {Function} func The function to debounce. * @param {number} [wait=0] * The number of milliseconds to delay; if omitted, `requestAnimationFrame` is * used (if available). * @param {Object} [options={}] The options object. * @param {boolean} [options.leading=false] * Specify invoking on the leading edge of the timeout. * @param {number} [options.maxWait] * The maximum time `func` is allowed to be delayed before it's invoked. * @param {boolean} [options.trailing=true] * Specify invoking on the trailing edge of the timeout. * @returns {Function} Returns the new debounced function. * @example * * // Avoid costly calculations while the window size is in flux. * jQuery(window).on('resize', debounce(calculateLayout, 150)) * * // Invoke `sendMail` when clicked, debouncing subsequent calls. * jQuery(element).on('click', debounce(sendMail, 300, { * 'leading': true, * 'trailing': false * })) * * // Ensure `batchLog` is invoked once after 1 second of debounced calls. * const debounced = debounce(batchLog, 250, { 'maxWait': 1000 }) * const source = new EventSource('/stream') * jQuery(source).on('message', debounced) * * // Cancel the trailing debounced invocation. * jQuery(window).on('popstate', debounced.cancel) * * // Check for pending invocations. * const status = debounced.pending() ? "Pending..." : "Ready" */ //创建一个去抖函数来推迟调用func,自从上一次去抖函数被调用之后等待wait毫秒时间过后再调用,或者等待直到下一次浏览器帧被重新绘制。创建去抖函数的同时也会创建一个cancel方法去取消延迟func调用,还有一个flush方法来立即调用。也提供了option参数来表明func函数是否应该在等待wait时间开始之前调用还是wait时间过后调用。func函数调用会带着提供给去抖函数的最后一个配置参数。之后对于去抖函数的调用返回最后一次func调用的结果。 //注意:如果leading和trailing配置项是true,func只有在去抖函数等待wait时间度过的时候被调用超过一次时才会在wait时延过后再次调用。 //如果wait等待时间是0,并且leading是false,func的调用会推迟至下一次事件循环,与setTimeout设置延迟时间为0一样 //如果wait参数在使用requestAnimationFrame的环境中被省略,func会延迟至下一次页面重绘的时候被调用,通常延迟16ms //使用场景 //当window大小不断变化的时候避免昂贵的计算,例如jQuery(window).on('resize', debounce(calculateLayout, 150)) //当点击的时候调用`sendMail`,将随后的调用去抖 // jQuery(element).on('click', debounce(sendMail, 300, { // 'leading': true, // 'trailing': false // })) //确保`batchLog`在去抖化调用后1秒钟被调用1次 // const debounced = debounce(batchLog, 250, { 'maxWait': 1000 }) // const source = new EventSource('/stream') // jQuery(source).on('message', debounced) //取消wait时延过后的去抖调用 //jQuery(window).on('popstate', debounced.cancel) //检查是否调用处于等待状态 //const status = debounced.pending() ? "Pending..." : "Ready" //func是需要去抖的方法 //wait推迟执行的毫秒数,如果省略这个参数,requestAnimationFrame会被使用 //requestAnimationFrame浏览器在下一次重绘之前调用指定的函数 //options中的配置项 //options.leading在wait时延之前调用 //options.trailing在wait时延过后调用 //options.maxWait在func被调用之前的最大延迟时间 function debounce(func, wait, options) { let lastArgs, lastThis, maxWait,//func调用之前的最大延迟时间 result, timerId,//定时器 lastCallTime let lastInvokeTime = 0//上一次调用func的时间 let leading = false//是否在wait时延之前调用 let maxing = false//是否设置了func调用之前的最大延迟时间 let trailing = true//是否在wait时延过后调用 // Bypass `requestAnimationFrame` by explicitly setting `wait=0`. //忽略requestAnimationFrame通过明确地设置wait=0 const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function') if (typeof func != 'function') {//如果func不是function类型,抛出错误 throw new TypeError('Expected a function') } wait = +wait || 0//将wait转换成数字 if (isObject(options)) {//如果options是对象 leading = !!options.leading//是否在wait时延之前调用 maxing = 'maxWait' in options//是否设置了func调用之前的最大延迟时间 maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait//func调用之前的最大延迟时间 trailing = 'trailing' in options ? !!options.trailing : trailing//是否在wait时延过后调用 } function invokeFunc(time) {//执行func const args = lastArgs const thisArg = lastThis lastArgs = lastThis = undefined//debounced接收参数和this置空 lastInvokeTime = time//更新上一次执行func时间 result = func.apply(thisArg, args)//调用func获取结果返回 return result } function startTimer(pendingFunc, wait) {//启动定时器 if (useRAF) { return root.requestAnimationFrame(pendingFunc) } return setTimeout(pendingFunc, wait) } function cancelTimer(id) {//取消定时器 if (useRAF) { return root.cancelAnimationFrame(id) } clearTimeout(id) } function leadingEdge(time) {//leading时调用 // Reset any `maxWait` timer. lastInvokeTime = time//重置上一次func执行时间 // Start the timer for the trailing edge. timerId = startTimer(timerExpired, wait)//为wait时延过后的trailing调用开启定时器 // Invoke the leading edge. return leading ? invokeFunc(time) : result//如果leading为true,wait时延之前调用,直接调用,否则返回result,undefined } function remainingWait(time) {//还剩下多少时间可以执行下一次 const timeSinceLastCall = time - lastCallTime//距离上次去抖时间 const timeSinceLastInvoke = time - lastInvokeTime//距离上次func执行时间 const timeWaiting = wait - timeSinceLastCall//距离上次去抖时间还需要等多久,下一次trailing时间 return maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting //如果有传递最大等待时间,就在下一次trailing时间和maxWait - timeSinceLastInvoke里找最小的 //如果没有传递最大等待时间,返回下一次trailing时间 } function shouldInvoke(time) {//判断在time时间点是否func应该被调用 const timeSinceLastCall = time - lastCallTime//距离上次去抖时间 const timeSinceLastInvoke = time - lastInvokeTime//距离上次func执行时间 // 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)) //lastCallTime === undefined首次调用 //timeSinceLastCall >= wait距离上次被去抖已经超过 wait //timeSinceLastCall < 0//系统时间倒退 //maxing && timeSinceLastInvoke >= maxWait//距离上次func执行时间已经超过最大等待时间 } function timerExpired() {//等待wait时间后触发trailing const time = Date.now()//当前时间 if (shouldInvoke(time)) {//如果当前时间应该调用,就调用trailingEdge判断是否已经去抖过一次 return trailingEdge(time) } // Restart the timer. timerId = startTimer(timerExpired, remainingWait(time))//如果此刻不能执行func,就重新启动定时器,时间为剩余等待时间 } function trailingEdge(time) {//trailing时调用func timerId = undefined//定时器置空 // Only invoke if we have `lastArgs` which means `func` has been // debounced at least once. if (trailing && lastArgs) {//只有在拥有lastArgs参数的时候才执行func,lastArgs说明func被去抖至少一次 return invokeFunc(time) } lastArgs = lastThis = undefined//lastArgs和lastThis置空 return result//返回结果undefined } function cancel() {//取消定时器,并将上一次调用时间清0,上一次debounced接收参数,this,上一次去抖时间都置空 if (timerId !== undefined) { cancelTimer(timerId) } lastInvokeTime = 0 lastArgs = lastCallTime = lastThis = timerId = undefined } function flush() {//如果没有定时器,返回当前结果,如果有定时器,调用trailingEdge return timerId === undefined ? result : trailingEdge(Date.now()) } function pending() {//查看当前是否处于等待状态 return timerId !== undefined } function debounced(...args) { const time = Date.now()//当前时间毫秒数 const isInvoking = shouldInvoke(time)//判断当前时间是否func应该被调用 lastArgs = args//参数数组 lastThis = this//本次this lastCallTime = time//去抖时间 if (isInvoking) {//如果此刻需要被调用,1说明是首次调用且是leading调用 2距离上次去抖wait时间了 3距离上次执行时间过去了最大等待时间 if (timerId === undefined) {//如果没有定时器说明1首次调用2刚执行过取消操作或者trailing调用 return leadingEdge(lastCallTime)//调用leadingEdge判断是否leading时需要调用 } if (maxing) {//如果不是第一次调用且设置了最大延迟时间,说明已经超过了最大延迟时间,直接调用返回结果 // Handle invocations in a tight loop. timerId = startTimer(timerExpired, wait)//开启一个定时器,等待时间wait,判断是否wait时间后需要出发trailing调用 return invokeFunc(lastCallTime)//调用func返回结果 } } if (timerId === undefined) {//如果此刻不需要被调用,且定时器没有开启,就开启一个定时器,等待时间wait timerId = startTimer(timerExpired, wait) } return result } debounced.cancel = cancel debounced.flush = flush debounced.pending = pending return debounced } export default debounce
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架