Debounce 和 Throttle 的原理及实现
在处理诸如 resize
、scroll
、mousemove
和 keydown/keyup/keypress
等事件的时候,通常我们不希望这些事件太过频繁地触发,尤其是监听程序中涉及到大量的计算或者有非常耗费资源的操作。
有多频繁呢?以 mousemove
为例,根据 DOM Level 3 的规定,「如果鼠标连续移动,那么浏览器就应该触发多个连续的 mousemove
事件」,这意味着浏览器会在其内部计时器允许的情况下,根据用户移动鼠标的速度来触发 mousemove
事件。(当然了,如果移动鼠标的速度足够快,比如“刷”一下扫过去,浏览器是不会触发这个事件的)。resize
、scroll
和 key*
等事件与此类似。
可以参看这个 Demo 体会下。
Debounce
DOM 事件里的 debounce
概念其实是从机械开关和继电器的“去弹跳”(debounce)衍生 出来的,基本思路就是把多个信号合并为一个信号。这篇文章 解释得非常清楚,感兴趣的可以一读。
在 JavaScript 中,debounce
函数所做的事情就是,强制一个函数在某个连续时间段内只执行一次,哪怕它本来会被调用多次。我们希望在用户停止某个操作一段时间之后才执行相应的监听函数,而不是在用户操作的过程当中,浏览器触发多少次事件,就执行多少次监听函数。
比如,在某个 3s 的时间段内连续地移动了鼠标,浏览器可能会触发几十(甚至几百)个 mousemove
事件,不使用 debounce
的话,监听函数就要执行这么多次;如果对监听函数使用 100ms 的“去弹跳”,那么浏览器只会执行一次这个监听函数,而且是在第 3.1s 的时候执行的。
现在,我们就来实现一个 debounce
函数。
实现
我们这个 debounce
函数接收两个参数,第一个是要“去弹跳”的回调函数 fn
,第二个是延迟的时间 delay
。
实际上,大部分的完整
debounce
实现还有第三个参数immediate
,表明回调函数是在一个时间区间的最开始执行(immediate
为true
)还是最后执行(immediate
为false
),比如 underscore 的 _.debounce。本文不考虑这个参数,只考虑最后执行的情况,感兴趣的可以自行研究。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
/** * * @param fn {Function} 实际要执行的函数 * @param delay {Number} 延迟时间,也就是阈值,单位是毫秒(ms) * * @return {Function} 返回一个“去弹跳”了的函数 */ function debounce(fn, delay) { // 定时器,用来 setTimeout var timer // 返回一个函数,这个函数会在一个时间区间结束后的 delay 毫秒时执行 fn 函数 return function () { // 保存函数调用时的上下文和参数,传递给 fn var context = this var args = arguments // 每次这个返回的函数被调用,就清除定时器,以保证不执行 fn clearTimeout(timer) // 当返回的函数被最后一次调用后(也就是用户停止了某个连续的操作), // 再过 delay 毫秒就执行 fn timer = setTimeout(function () { fn.apply(context, args) }, delay) } } |
其实思路很简单,debounce
返回了一个闭包,这个闭包依然会被连续频繁地调用,但是在闭包内部,却限制了原始函数 fn
的执行,强制 fn
只在连续操作停止后只执行一次。
debounce
的使用方式如下:
1 2 3 |
$(document).on('mouvemove', debounce(function(e) { // 代码 }, 250)) |
用例
还是以 mousemove
为例,为其绑定一个“去弹跳”的监听器,效果是怎样的?请看这个 Demo。
再来考虑另外一个场景:根据用户的输入实时向服务器发 ajax 请求获取数据。我们知道,浏览器触发 key*
事件也是非常快的,即便是正常人的正常打字速度,key*
事件被触发的频率也是很高的。以这种频率发送请求,一是我们并没有拿到用户的完整输入发送给服务器,二是这种频繁的无用请求实在没有必要。
更合理的处理方式是,在用户“停止”输入一小段时间以后,再发送请求。那么 debounce
就派上用场了:
1 2 3 |
$('input').on('keyup', debounce(function(e) { // 发送 ajax 请求 }, 300)) |
可以查看这个 Demo 看看效果。
Throttle
throttle
的概念理解起来更容易,就是固定函数执行的速率,即所谓的“节流”。正常情况下,mousemove
的监听函数可能会每 20ms(假设)执行一次,如果设置 200ms 的“节流”,那么它就会每 200ms 执行一次。比如在 1s 的时间段内,正常的监听函数可能会执行 50(1000/20) 次,“节流” 200ms 后则会执行 5(1000/200) 次。
我们先来看 Demo。可以看到,不管鼠标移动的速度是慢是快,“节流”后的监听函数都会“匀速”地每 250ms 执行一次。
实现
与 debounce
类似,我们这个 throttle
也接收两个参数,一个实际要执行的函数 fn
,一个执行间隔阈值 threshhold
。
同样的,
throttle
的更完整实现可以参看 underscore 的 _.throttle。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/** * * @param fn {Function} 实际要执行的函数 * @param delay {Number} 执行间隔,单位是毫秒(ms) * * @return {Function} 返回一个“节流”函数 */ function throttle(fn, threshhold) { // 记录上次执行的时间 var last // 定时器 var timer // 默认间隔为 250ms threshhold || (threshhold = 250) // 返回的函数,每过 threshhold 毫秒就执行一次 fn 函数 return function () { // 保存函数调用时的上下文和参数,传递给 fn var context = this var args = arguments var now = +new Date() // 如果距离上次执行 fn 函数的时间小于 threshhold,那么就放弃 // 执行 fn,并重新计时 if (last && now < last + threshhold) { clearTimeout(timer) // 保证在当前时间区间结束后,再执行一次 fn timer = setTimeout(function () { last = now fn.apply(context, args) }, threshhold) // 在时间区间的最开始和到达指定间隔的时候执行一次 fn } else { last = now fn.apply(context, args) } } } |
原理也不复杂,相比 debounce
,无非是多了一个时间间隔的判断,其他的逻辑基本一致。throttle
的使用方式如下:
1 2 3 |
$(document).on('mouvemove', throttle(function(e) { // 代码 }, 250)) |
用例
throttle
常用的场景是限制 resize
和 scroll
的触发频率。以 scroll
为例,查看这个 Demo 感受下。
可视化解释
如果还是不能完全体会 debounce
和 throttle
的差异,可以到 这个页面 看一下两者可视化的比较。
总结
debounce
强制函数在某段时间内只执行一次,throttle
强制函数以固定的速率执行。在处理一些高频率触发的 DOM 事件的时候,它们都能极大提高用户体验。
Debounce 和 Throttle 的原理及实现相关推荐
- 白话debounce和throttle
遇到的问题 在开发过程中会遇到频率很高的事件或者连续的事件,如果不进行性能的优化,就可能会出现页面卡顿的现象,比如: 鼠标事件:mousemove(拖曳)/mouseover(划过)/mouseWhe ...
- debounce和throttle
性能和速度是程序的敌人,以致于每一个优秀的程序员都在孜孜不倦的提升软件的性能和速度,从而提升产品的用户体验. 下面介绍的是debounce和throttle,这两种技术能够改善程序的性能,它们非常相似 ...
- debounce vs throttle
debounce vs throttle debounce假设你正在乘电梯上楼,当电梯门关闭之前发现有人也要乘电梯,礼貌起见,你会按下开门开关,然后等他进电梯:如果在电梯门关闭之前,又有人来了,你会继 ...
- 浅析Debounce 与 Throttle的区别
文章目录 概述 Debounce 瞬时响应or延迟响应 举例与注意事项 Throttle 定义 用法举例 参考文章 这两天在学习前端知识,在Vue的官方教程中看到了这两个概念,查阅相关资料后,做以下整 ...
- debounce 与throttle
背景 开发过程中我们会遇到一些场景,事件频繁被触发,导致频繁的DOM操作等,导致UI卡顿 甚至浏览器崩溃等. 1.window对象的resize.scroll 事件 2.拖拽时的mousemove事件 ...
- SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍
这是Jerry 2021年的第 11 篇文章,也是汪子熙公众号总共第 282 篇原创文章. Jerry之前的文章 SAP UI5 OData谣言粉碎机:极短时间内发送两个Odata request, ...
- 前端间隔查询的两种方法:Debounce和Throttle
Debounce 中文名:防抖.在开始操作了之后,那么只有在一段 delay 时间段后不再有操作了,才执行操作. Throttle 中文名:节流.在开始操作之后,在 delay ms 内只会做一次.会 ...
- debounce与throttle区别
在2011年,Twitter网站曾爆出一个问题:在主页往下滚动时,页面会变得缓慢以致没有响应.John Resig发表了一篇文章< a blog post about the problem&g ...
- debounce、throttle、useDebounce、useThrottle
直接使用lodash的debounce会出现以下报错信息 This synthetic event is reused for performance reasons. If you're seein ...
最新文章
- 网络推广期间发现网站快照更新过慢会影响正常网络推广吗?
- ThinkPHP--栏目增删改查ADSF
- 使用randomaccessfile类将一个文本文件中的内容逆序输出_Java IO2:RandomAccessFile
- 小米集团:回购460万股,耗资9818万港元
- 字符串的迷之算法——KMP,AC自动机,后缀数组
- 捕获键盘和鼠标的消息机制
- WAVE-U-NET: A MULTI-SCALE NEURAL NETWORK FOR END-TO-END AUDIO SOURCE SEPARATION
- python语言程序设计实践教程答案上海交通大学_Python推荐书籍从入门到进阶(珍藏版)...
- python上的表白代码_用Python实现表白代码
- css鼠标经过盒子,盒子向上移动动画
- 自强不息系列之Python 选择排序
- Python3.7入门什么Bug?Bug最基本的调试Debug
- (附完整代码和实验报告)【python 大作业】实现一个聊天机器人,关键词双重查找,结合语境查找,爬虫查询,图形化界面,语音播报。
- 警校计算机专业考研,一名警校毕业生的跨专业考研路
- 前端学习CSS3(day3)盾牌案例
- 【Django】Django Auth认证组件详述
- html网页制作注意事项,使用网页设计模板的注意事项-
- 支付后但是显示未支付,再次支付如果又支付成功了怎么办? 后端代码逻辑是什么?【杭州多测师_王sir】【杭州多测师】...
- 再见了!摩托罗拉:从独领风骚到一败涂地
- Tvori推出2.0版本,让VR动画制作更简单
热门文章
- umi +qiankun 主应用动态装载子应用(路由)解决方案
- 阿里巴巴Java代码规范
- 抛物线法matlab实验,数值分析:牛顿、弦歌、抛物线法的matlab实现
- 流放者柯南自建服务器 linux,流放者柯南个人服务器修改怎么配置 个人服务器修改配置路径介绍...
- 魅蓝x android,魅蓝X的内存容量是多少?运存ram是多少?
- Android开发—华为手机应用内升级之后App图标消失
- 【钻石展位】无线链接要求规则解读
- Audio Unit: iOS中最底层最强大音频控制API
- Pico两通道汽车诊断示波器标准套装(型号:PQ177)
- Qualifier注解