函数去抖(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

相关文章

Python中help()和dir()函数的使用

《Python中help()和dir()函数的使用》我们经常需要查看某个对象(如模块、类、函数等)的属性和方法,Python提供了两个内置函数help()和dir(),它们可以帮助我们快速了解代... 目录1. 引言2. help() 函数2.1 作用2.2 使用方法2.3 示例(1) 查看内置函数的帮助(

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

SQL中JOIN操作的条件使用总结与实践

《SQL中JOIN操作的条件使用总结与实践》在SQL查询中,JOIN操作是多表关联的核心工具,本文将从原理,场景和最佳实践三个方面总结JOIN条件的使用规则,希望可以帮助开发者精准控制查询逻辑... 目录一、ON与WHERE的本质区别二、场景化条件使用规则三、最佳实践建议1.优先使用ON条件2.WHERE用

Nginx Location映射规则总结归纳与最佳实践

《NginxLocation映射规则总结归纳与最佳实践》Nginx的location指令是配置请求路由的核心机制,其匹配规则直接影响请求的处理流程,下面给大家介绍NginxLocation映射规则... 目录一、Location匹配规则与优先级1. 匹配模式2. 优先级顺序3. 匹配示例二、Proxy_pa

Python中bisect_left 函数实现高效插入与有序列表管理

《Python中bisect_left函数实现高效插入与有序列表管理》Python的bisect_left函数通过二分查找高效定位有序列表插入位置,与bisect_right的区别在于处理重复元素时... 目录一、bisect_left 基本介绍1.1 函数定义1.2 核心功能二、bisect_left 与

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

java中BigDecimal里面的subtract函数介绍及实现方法

《java中BigDecimal里面的subtract函数介绍及实现方法》在Java中实现减法操作需要根据数据类型选择不同方法,主要分为数值型减法和字符串减法两种场景,本文给大家介绍java中BigD... 目录Java中BigDecimal里面的subtract函数的意思?一、数值型减法(高精度计算)1.

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一

Python函数返回多个值的多种方法小结

《Python函数返回多个值的多种方法小结》在Python中,函数通常用于封装一段代码,使其可以重复调用,有时,我们希望一个函数能够返回多个值,Python提供了几种不同的方法来实现这一点,需要的朋友... 目录一、使用元组(Tuple):二、使用列表(list)三、使用字典(Dictionary)四、 使