函数去抖(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常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

python使用try函数详解

《python使用try函数详解》Pythontry语句用于异常处理,支持捕获特定/多种异常、else/final子句确保资源释放,结合with语句自动清理,可自定义异常及嵌套结构,灵活应对错误场景... 目录try 函数的基本语法捕获特定异常捕获多个异常使用 else 子句使用 finally 子句捕获所

Spring Boot 与微服务入门实战详细总结

《SpringBoot与微服务入门实战详细总结》本文讲解SpringBoot框架的核心特性如快速构建、自动配置、零XML与微服务架构的定义、演进及优缺点,涵盖开发环境准备和HelloWorld实战... 目录一、Spring Boot 核心概述二、微服务架构详解1. 微服务的定义与演进2. 微服务的优缺点三

postgresql使用UUID函数的方法

《postgresql使用UUID函数的方法》本文给大家介绍postgresql使用UUID函数的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录PostgreSQL有两种生成uuid的方法。可以先通过sql查看是否已安装扩展函数,和可以安装的扩展函数

MySQL字符串常用函数详解

《MySQL字符串常用函数详解》本文给大家介绍MySQL字符串常用函数,本文结合实例代码给大家介绍的非常详细,对大家学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql字符串常用函数一、获取二、大小写转换三、拼接四、截取五、比较、反转、替换六、去空白、填充MySQL字符串常用函数一、

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

MySql基本查询之表的增删查改+聚合函数案例详解

《MySql基本查询之表的增删查改+聚合函数案例详解》本文详解SQL的CURD操作INSERT用于数据插入(单行/多行及冲突处理),SELECT实现数据检索(列选择、条件过滤、排序分页),UPDATE... 目录一、Create1.1 单行数据 + 全列插入1.2 多行数据 + 指定列插入1.3 插入否则更

PostgreSQL中rank()窗口函数实用指南与示例

《PostgreSQL中rank()窗口函数实用指南与示例》在数据分析和数据库管理中,经常需要对数据进行排名操作,PostgreSQL提供了强大的窗口函数rank(),可以方便地对结果集中的行进行排名... 目录一、rank()函数简介二、基础示例:部门内员工薪资排名示例数据排名查询三、高级应用示例1. 每

全面掌握 SQL 中的 DATEDIFF函数及用法最佳实践

《全面掌握SQL中的DATEDIFF函数及用法最佳实践》本文解析DATEDIFF在不同数据库中的差异,强调其边界计算原理,探讨应用场景及陷阱,推荐根据需求选择TIMESTAMPDIFF或inte... 目录1. 核心概念:DATEDIFF 究竟在计算什么?2. 主流数据库中的 DATEDIFF 实现2.1

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串