函数去抖(debounce) 函数节流(throttle)总结

2024-03-23 04:58

本文主要是介绍函数去抖(debounce) 函数节流(throttle)总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  • 1. 什么是函数去抖 & 函数节流
    • debounce使用场景
    • throttle使用场景
  • 2. 实现方法&应用
    • a. 简单实现
      • debounce
      • throttle
    • b. 附:Lodash实现
      • debounce
      • throttle
    • c. 附:Underscore实现
      • debounce
      • throttle


1. 什么是函数去抖 & 函数节流

让某个函数在一定 事件间隔条件(去抖debounce) 或 时间间隔条件(节流throttle) 下才会去执行,避免快速多次执行函数(操作DOM,加载资源等等)给内存带来大量的消耗从而一定程度上降低性能问题。

debounce: 当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。
throttle:预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。

debounce使用场景

  1. scroll事件(资源的加载)
  2. mousemove事件(拖拽)
  3. resize事件(响应式布局样式)
  4. keyup事件(输入框文字停止打字后才进行校验)

debounce电梯:
假设你正在准备乘坐电梯,并且电梯门准备关上然后上升的时候,你的同事来了,出于礼貌,我们需要停止电梯的关闭,让同事进入.假设源源不断的有同事进来的话,电梯就需要处于一种待机的状态,一直等待人员的进入,直到没有新的同事进入或者说电梯满了,这个时候,电梯才能运行.另外,同事的进入需要在电梯门的关闭之前,否则的话,就只能等下一趟了。换成图示我们可以这么理解:
debounce电梯

throttle使用场景

  1. click事件(不停快速点击按钮,减少触发频次)
  2. scroll事件(返回顶部按钮出现\隐藏事件触发)
  3. keyup事件(输入框文字与显示栏内容复制同步)
  4. 减少发送ajax请求,降低请求频率

throttle电梯:
throttle电梯不想debounce电梯一样会无限的等待,而是我们设定一个时间,例如10s,那么10s内,其他的人可以不断的进入电梯,但是,一旦10s过去了,那么无论如何,电梯都会进入运行的状态。换成图示,我们可以这么理解:
throttle电梯


2. 实现方法&应用

首先是自己写的各自简易的实现,然后对比理解Lodash实现的复杂版本。看完你会发现节流本质上是去抖的一种特殊实现。

a. 简单实现

debounce

.html

<button id="btn">click</button>
<div id="display"></div>

.js

/*** 防反跳。fn函数在最后一次调用时刻的delay毫秒之后执行!* @param fn 执行函数* @param delay 时间间隔* @param isImmediate 为true,debounce会在delay时间间隔的开始时立即调用这个函数* @returns {Function}*/
function debounce(fn, delay, isImmediate) {var timer = null;  //初始化timer,作为计时清除依据return function() {var context = this;  //获取函数所在作用域thisvar args = arguments;  //取得传入参数clearTimeout(timer);if(isImmediate && timer === null) {//时间间隔外立即执行fn.apply(context,args);timer = 0;return;}timer = setTimeout(function() {fn.apply(context,args);timer = null;}, delay);}
}/* 方法执行e.g. */
var btn = document.getElementById('btn');
var el = document.getElementById('display');
var init = 0;
btn.addEventListener('click', debounce(function() {init++;el.innerText = init;
}, 1000,true));

说明:
这里实现了一个有去抖功能的计数器。该函数接收三个参数,分别是要执行的函数fn、事件完成周期时间间隔delay(即事件间隔多少时间内不再重复触发)以及是否在触发周期内立即执行isImmediate。需要注意的是要给执行函数绑定一个调用函数的上下文以及对应传入的参数。示例中对click事件进行了去抖,间隔时间为1000毫秒,为立即触发方式,当不停点击按钮时,第一次为立即触发,之后直到最后一次点击事件结束间隔delay秒后开始执行加1操作。

⇒ Demo
debounce

throttle

.html
(同上)

.js

/*** 创建并返回一个像节流阀一样的函数,当重复调用函数的时候,最多每隔delay毫秒调用一次该函数* @param fn 执行函数* @param delay 时间间隔* @returns {Function}*/
function throttle(fn, delay) {var timer = null;var timeStamp = new Date();return function() {var context = this;  //获取函数所在作用域thisvar args = arguments;  //取得传入参数if(new Date()-timeStamp>delay){timeStamp = new Date();timer = setTimeout(function(){fn.apply(context,args);},delay);}}
}/* 方法执行 */
var btn = document.getElementById('btn');
var el = document.getElementById('display');
var init = 0;
btn.addEventListener('click', throttle(function() {init++;el.innerText = init;
}, 1000));

说明:
这里实现了一个简易的有去节流功能的计数器。该函数接收两个参数,分别是要执行的函数fn、事件完成周期时间间隔delay(即事件间隔多少时间内不再重复触发)。需要注意的是要给执行函数绑定一个调用函数的上下文以及对应传入的参数,以及在闭包外层的timeStamp时间记录戳,用于判断事件的时间间隔。示例中对click事件进行了节流,间隔时间为1000毫秒,不停点击按钮,计数器会间隔1秒时间进行加1操作。

缺点:
没有控制事件的头尾选项,即没有控制是否在连续事件的一开始及最终位置是否需要执行。(参考underscore弥补)

⇒ Demo
debounce

b. 附:Lodash实现

debounce

var isObject = require('./isObject'),now = require('./now'),toNumber = require('./toNumber');/** Error message constants. */
var FUNC_ERROR_TEXT = 'Expected a function';/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeMax = Math.max,nativeMin = Math.min;/*** Creates a debounced function that delays invoking `func` until after `wait`* milliseconds have elapsed since the last time the debounced function was* invoked. 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 to the next tick, similar to `setTimeout` with a timeout of `0`.** See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)* for details over the differences between `_.debounce` and `_.throttle`.** @static* @memberOf _* @since 0.1.0* @category Function* @param {Function} func The function to debounce.* @param {number} [wait=0] The number of milliseconds to delay.* @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.* 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);*/
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,result = wait - timeSinceLastCall;return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;}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.timerId = setTimeout(timerExpired, wait);return invokeFunc(lastCallTime);}}if (timerId === undefined) {timerId = setTimeout(timerExpired, wait);}return result;}debounced.cancel = cancel;debounced.flush = flush;return debounced;
}module.exports = debounce;

throttle

var debounce = require('./debounce'),isObject = require('./isObject');/** Error message constants. */
var FUNC_ERROR_TEXT = 'Expected a function';/*** Creates a throttled function that only invokes `func` at most once per* every `wait` milliseconds. The throttled 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* throttled function. Subsequent calls to the throttled 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 throttled function* is invoked more than once during the `wait` timeout.** If `wait` is `0` and `leading` is `false`, `func` invocation is deferred* until to the next tick, similar to `setTimeout` with a timeout of `0`.** See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)* for details over the differences between `_.throttle` and `_.debounce`.** @static* @memberOf _* @since 0.1.0* @category Function* @param {Function} func The function to throttle.* @param {number} [wait=0] The number of milliseconds to throttle invocations to.* @param {Object} [options={}] The options object.* @param {boolean} [options.leading=true]*  Specify invoking on the leading edge of the timeout.* @param {boolean} [options.trailing=true]*  Specify invoking on the trailing edge of the timeout.* @returns {Function} Returns the new throttled function.* @example** // Avoid excessively updating the position while scrolling.* jQuery(window).on('scroll', _.throttle(updatePosition, 100));** // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.* var throttled = _.throttle(renewToken, 300000, { 'trailing': false });* jQuery(element).on('click', throttled);** // Cancel the trailing throttled invocation.* jQuery(window).on('popstate', throttled.cancel);*/
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});
}module.exports = throttle;

c. 附:Underscore实现

debounce

/*** 防反跳。func函数在最后一次调用时刻的wait毫秒之后执行!* @param func 执行函数* @param wait 时间间隔* @param immediate 为true,debounce会在wai 时间间隔的开始调用这个函数* @returns {Function}*/
function debounce(func, wait, immediate) {var timeout, args, context, timestamp, result;var later = function() {var last = new Date().getTime() - timestamp; // timestamp会实时更新if (last < wait && last >= 0) {timeout = setTimeout(later, wait - last);} else {timeout = null;if (!immediate) {result = func.apply(context, args);if (!timeout) context = args = null;}}};return function() {context = this;args = arguments;timestamp = new Date().getTime();var callNow = immediate && !timeout;if (!timeout) {timeout = setTimeout(later, wait);}if (callNow) {result = func.apply(context, args);context = args = null;}return result;};
}

throttle

/*** 创建并返回一个像节流阀一样的函数,当重复调用函数的时候,最多每隔 wait毫秒调用一次该函数* @param func 执行函数* @param wait 时间间隔* @param options 如果你想禁用第一次首先执行的话,传递{leading: false},*                如果你想禁用最后一次执行的话,传递{trailing: false}* @returns {Function}*/
function throttle(func, wait, options) {var context, args, result;var timeout = null;var previous = 0;if (!options) options = {};var later = function() {previous = options.leading === false ? 0 : new Date().getTime();timeout = null;result = func.apply(context, args);if (!timeout) context = args = null;};return function() {var now = new Date().getTime();if (!previous && options.leading === false) previous = now;var remaining = wait - (now - previous);context = this;args = arguments;if (remaining <= 0 || remaining > wait) {if (timeout) {clearTimeout(timeout);timeout = null;}previous = now;result = func.apply(context, args);if (!timeout) context = args = null;} else if (!timeout && options.trailing !== false) {timeout = setTimeout(later, remaining);}return result;};
}

[refs]
浅谈throttle以及debounce的原理和实现
debounce & throttle的区别
Lodash - _.debounce 中文文档

这篇关于函数去抖(debounce) 函数节流(throttle)总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/837200

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的

二分最大匹配总结

HDU 2444  黑白染色 ,二分图判定 const int maxn = 208 ;vector<int> g[maxn] ;int n ;bool vis[maxn] ;int match[maxn] ;;int color[maxn] ;int setcolor(int u , int c){color[u] = c ;for(vector<int>::iter

整数Hash散列总结

方法:    step1  :线性探测  step2 散列   当 h(k)位置已经存储有元素的时候,依次探查(h(k)+i) mod S, i=1,2,3…,直到找到空的存储单元为止。其中,S为 数组长度。 HDU 1496   a*x1^2+b*x2^2+c*x3^2+d*x4^2=0 。 x在 [-100,100] 解的个数  const int MaxN = 3000

状态dp总结

zoj 3631  N 个数中选若干数和(只能选一次)<=M 的最大值 const int Max_N = 38 ;int a[1<<16] , b[1<<16] , x[Max_N] , e[Max_N] ;void GetNum(int g[] , int n , int s[] , int &m){ int i , j , t ;m = 0 ;for(i = 0 ;

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

函数式编程思想

我们经常会用到各种各样的编程思想,例如面向过程、面向对象。不过笔者在该博客简单介绍一下函数式编程思想. 如果对函数式编程思想进行概括,就是f(x) = na(x) , y=uf(x)…至于其他的编程思想,可能是y=a(x)+b(x)+c(x)…,也有可能是y=f(x)=f(x)/a + f(x)/b+f(x)/c… 面向过程的指令式编程 面向过程,简单理解就是y=a(x)+b(x)+c(x)

go基础知识归纳总结

无缓冲的 channel 和有缓冲的 channel 的区别? 在 Go 语言中,channel 是用来在 goroutines 之间传递数据的主要机制。它们有两种类型:无缓冲的 channel 和有缓冲的 channel。 无缓冲的 channel 行为:无缓冲的 channel 是一种同步的通信方式,发送和接收必须同时发生。如果一个 goroutine 试图通过无缓冲 channel