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

相关文章

MySQL 中的 CAST 函数详解及常见用法

《MySQL中的CAST函数详解及常见用法》CAST函数是MySQL中用于数据类型转换的重要函数,它允许你将一个值从一种数据类型转换为另一种数据类型,本文给大家介绍MySQL中的CAST... 目录mysql 中的 CAST 函数详解一、基本语法二、支持的数据类型三、常见用法示例1. 字符串转数字2. 数字

Python内置函数之classmethod函数使用详解

《Python内置函数之classmethod函数使用详解》:本文主要介绍Python内置函数之classmethod函数使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 类方法定义与基本语法2. 类方法 vs 实例方法 vs 静态方法3. 核心特性与用法(1编程客

Python函数作用域示例详解

《Python函数作用域示例详解》本文介绍了Python中的LEGB作用域规则,详细解析了变量查找的四个层级,通过具体代码示例,展示了各层级的变量访问规则和特性,对python函数作用域相关知识感兴趣... 目录一、LEGB 规则二、作用域实例2.1 局部作用域(Local)2.2 闭包作用域(Enclos

JavaSE正则表达式用法总结大全

《JavaSE正则表达式用法总结大全》正则表达式就是由一些特定的字符组成,代表的是一个规则,:本文主要介绍JavaSE正则表达式用法的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录常用的正则表达式匹配符正则表China编程达式常用的类Pattern类Matcher类PatternSynta

MySQL count()聚合函数详解

《MySQLcount()聚合函数详解》MySQL中的COUNT()函数,它是SQL中最常用的聚合函数之一,用于计算表中符合特定条件的行数,本文给大家介绍MySQLcount()聚合函数,感兴趣的朋... 目录核心功能语法形式重要特性与行为如何选择使用哪种形式?总结深入剖析一下 mysql 中的 COUNT

MySQL 中 ROW_NUMBER() 函数最佳实践

《MySQL中ROW_NUMBER()函数最佳实践》MySQL中ROW_NUMBER()函数,作为窗口函数为每行分配唯一连续序号,区别于RANK()和DENSE_RANK(),特别适合分页、去重... 目录mysql 中 ROW_NUMBER() 函数详解一、基础语法二、核心特点三、典型应用场景1. 数据分

MySQL数据库的内嵌函数和联合查询实例代码

《MySQL数据库的内嵌函数和联合查询实例代码》联合查询是一种将多个查询结果组合在一起的方法,通常使用UNION、UNIONALL、INTERSECT和EXCEPT关键字,下面:本文主要介绍MyS... 目录一.数据库的内嵌函数1.1聚合函数COUNT([DISTINCT] expr)SUM([DISTIN

Python get()函数用法案例详解

《Pythonget()函数用法案例详解》在Python中,get()是字典(dict)类型的内置方法,用于安全地获取字典中指定键对应的值,它的核心作用是避免因访问不存在的键而引发KeyError错... 目录简介基本语法一、用法二、案例:安全访问未知键三、案例:配置参数默认值简介python是一种高级编

python 常见数学公式函数使用详解(最新推荐)

《python常见数学公式函数使用详解(最新推荐)》文章介绍了Python的数学计算工具,涵盖内置函数、math/cmath标准库及numpy/scipy/sympy第三方库,支持从基础算术到复杂数... 目录python 数学公式与函数大全1. 基本数学运算1.1 算术运算1.2 分数与小数2. 数学函数

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

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