且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍

更新时间:2022-09-13 12:32:44

Angular

641 篇文章10 订阅

订阅专栏

这是Jerry 2021年的第 11 篇文章,也是汪子熙公众号总共第 282 篇原创文章。


Jerry之前的文章 SAP UI5 OData谣言粉碎机:极短时间内发送两个Odata request, 前一个会自动被cancel掉吗,介绍过SAP成都研究院CRM Fiori开发团队开发过的一个Live Search的场景。


用户创建Opportunity,维护Account字段,每输入一个字符,都会触发SAP UI5 Input控件的liveChange事件。在该事件的onAccountInputFieldChanged处理函数里,根据用户输入,发送OData请求到后台进行查询。


SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍最近Jerry做SAP Spartacus开发,遇到了同样的场景。因此通过本文把自己最近所学总结一下,记录下SAP UI5和Angular里如何使用函数防抖(Debounce)和函数节流(Throttle)来避免短时间内触发高频次函数调用的情况出现。


为了便于讲解,Jerry做了一个只包含一个Input控件的SAP UI5页面。源代码地址.


在Input里输入字符,会触发liveChange事件,将当前Input的最新内容,发送到一个我自己开发的后台服务去。该后台服务什么也不做,只是简单将收到的内容返回给UI.


SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍SAP UI5如何使用函数防抖(Debounce)来降低函数调用的频次

函数防抖(Debounce),最早源于机械开关和继电器的术语“去弹跳”,即将多个信号合并为一个信号。


想象一个大家现实生活中都会遇到的场景:进电梯。电梯都有一个自动关闭门的超时时间,假设为2秒。当电梯检测到有人进入时,会重置这个2秒的计时器。如果下一个2秒之内,没有新的乘客进入电梯,电梯门才会自动关上。



SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍电梯延迟关门这个场景,就是一个典型的函数防抖的现实例子。电梯关门的行为就是“函数”,通过电梯门的自动关闭超时时间,2秒,来延迟电梯门的关闭动作的执行,从而降低电梯门的关闭频率,这就是“防抖”。


可以想象,如果电梯门的自动关闭没有设定超时时间,而是检测到没有人进出之后,立即关闭,这样会大大增加电梯门开合的频率,既浪费能源,也不安全。这就好比Jerry本文开头提到的例子:既然我短时间内输入了字符1234,我期望在UI看到的,是后台服务接收到1234后返回的结果。至于后台如何对前三个请求,即字符1,字符12和字符123进行处理,我不再关心。


我们可以仿照电梯门关闭超时时间的设定,来给SAP UI5的函数调用实现防抖控制。


下图debounce变量是一个函数构造器,本身是一个函数,接收另一个函数fn作为输入参数,职责是通过闭包,将fn改造成一个具有防抖控制功能的新函数,该新函数通过第17行的return语句返回。


防抖时间间隔通过函数构造器另一个输入参数delay指定。


SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍假设我们指定的防抖时间间隔为3000毫秒即3秒,如果3秒之内,debounce函数构造器返回的新函数被不断调用,此时执行上图代码第19行,调用clearTimeout重置计数器,此时原始函数fn不会得到执行。这个场景可以类比成:在电梯关门超时时间内,又有新的乘客进入,电梯超时计时器重置,电梯门不会关闭。


代码第20行,使用setTimeout重启超时时间间隔为3秒的计数器,3秒过后,如果JavaScript任务队列里没有其他待执行任务,则执行原始函数fn. 代码的第20行,好比电梯设备重新开启了3秒的超时定时器。


如果在等待的3秒之内,没有新的函数调用触发,则3秒过后,执行21行的原始函数fn;这好比电梯在3秒之内,始终没有新的乘客进入,则 3秒过后,电梯门自动关闭。


debounce函数构造器的使用方式也很简单。


代码第78行,将原始的sendRequest函数,以及3000毫秒的防抖时间间隔,传入debounce构造器,返回一个兼有数据发送功能和防抖功能的debounceVersion函数。在第85行原来调用sendRequest函数的位置,改为调用debounceVersion函数即可。


SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍SAP UI5如何使用函数节流(Throttle)来降低函数调用的频次

上述函数防抖的实现存在一个问题,还是以电梯的例子来说明。


设想有一个空间无限的电梯,关门的超时时间为3秒。如果不断的有新的乘客以小于3秒的时间间隔进入电梯,则电梯门永远没有机会关闭——即函数永远得不到执行。


函数节流(Throttle)是另一种降低函数调用频次的思路,同函数防抖的区别是,后者能保证在指定的节流间隔内,至少执行一次函数。


函数节流构造器的一个最简单的实现版本:


被节流器改造后的函数每次触发时,取一个当前系统时间戳,同前一次触发时取的时间戳比较。如果二者的时间差,大于等于构造器的输入参数delay即节流时间间隔,则进入第39行的else分支,触发原始函数fn;否则说明节流时间间隔还未到达,使用第34行setTimeout,将原始函数fn,重新放入JavaScript事件队列内,延迟执行:


SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍函数节流版本的构造器使用方式,同函数防抖版本的构造器没有差别:将原始函数sendRequest传入构造器throttle,返回一个具有节流功能的新函数throttleVersion,在Input控件liveChange事件处理函数里,调用throttleVersion这个新函数即可。


SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍本文介绍的两种函数防抖和函数节流的实现代码,仅仅考虑了最基本的情况,还有很多不完善的地方,有兴趣的朋友可以在网络上搜索,这方面的资料非常多,这里不再赘述。


Jerry之前的分享提到过,Angular是响应式编程开发库RxJS的重度使用者,后者提供了众多功能强大的Operators,使得Angular开发人员不用重复造***,就能轻易实现出具有函数防抖和函数节流的场景。


SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍用Angular重新实现本文SAP UI5的Demo,总共代码只有44行:SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍类似SAP UI5 Input控件的liveChange,Angular FormControl的valueChanges也给应用开发人员提供了编写业务逻辑,响应用户输入的位置:后者的valueChanges数据类型是Observable,应用开发人员可以通过pipe调用,传入RxJS各种功能强大的Operators,让自己编写的包含业务逻辑的事件响应函数,按照实际需求来触发。


SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍比如上图第39行代码,语义是:绑定到jerryFormControl的input控件有valueChanges发生时,首先经过防抖器的处理。至于是否能够满足触发valueChanges对应的事件处理函数的条件,由防抖器debounceTime的内部处理逻辑决定。


RxJS防抖器debounceTime的内部实现使用了setInterval,逻辑比Jerry本文介绍的debounce函数构造器复杂得多了,通过这些调用栈就能感受一二:


SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍