节流函数
节流函数算是老生常谈了,项目里面必备的基础函数,但你真的用对了吗?深入研究了一下,发现自己用的确实不太对。首先了解一下节流函数的概念
节流函数:顾名思义,像节流阀一样均匀的执行,把密集的重复调用,通过设置定时器,使函数在固定的间隔时间调用,在一段时间内,等时间间隔的调用多次,这里跟的bounce函数一定要区分开。
debounce: 中文意思,防反跳,防止密集的重复请求,即一定时间间隔内的密集请求都丢弃,等待间隔时间后不再请求再去执行,在一段时间内,可能只调用一次。
分清楚这两个概念之后,先看一段代码:
1 | const generateThrottle = function (throttleTime) { |
这是我们项目里面的节流函数,咋一看没啥问题,直到项目出现了一个很偶现的bug,最后发现是dosomething()这个函数偶尔会不执行引起的。我们再来仔细看一下代码,当时间间隔大于THROTTLE_TIME时函数返回ture,否则就不返回,如果最后一次缩放窗口和上次时间间隔小于THROTTLE_TIME,然后停止缩放,那么这个时候dosomething()就不执行了,最后导致dosomething的状态还停留在上一次缩放,导致bug。所以当你希望停止缩放后再执行一次dosomething(),那么上面这个函数显然是不合理的!
然后我们重写了这个节流函数如下:
1 | /** |
1 | function testFn () { |
这个函数首次调用会创建一个定时器,当重复调用的时候会不断的清空并重置定时器,当时间间隔大于等于mustRunDelay时,调用该函数,并更新时间戳。这样就保证了函数每隔mustRunDelay后重复调用该函数,并且在最后一次触发之后的delay时间后最后一次执行该函数。这个函数就基本能满足我们业务的需求了。如果你想首次调用能够立即执行该函数或者最后一次调用并不触发该函数,那么继续往下看。
1 | /** |
1 | function testFn () { |
这个节流函数就相当的完美了,默认是首次立即执行fn,最后一次调用之后的wait时间后执行,当重复调用的时候,最多每隔wait时间间隔调用一次fn。也可以通过传参来禁用第一次执行和最后一次执行。
debounce函数
然后我们再来看看高程上面的节流函数,函数每次调用的时候先清空定时器,再设置一个新的定时器,当窗口缩放的时候会不断的触发throttle,这个时候实际上fn只有在最后一次触发throttle后100ms执行,而且只执行一次。所以这个并不是真正意义上面的节流函数。而是一个debounce函数,丢弃一些密集重复的操作。
1 | function throttle (method, context) { |
下面这个函数写法不一样,其实结果也是一样,只执行一次
1 | let throttle = function(fn, delay){ |
1 | function testFn () { |
debounce函数的使用场景很多,比如用户输入验证,在用户的输入过程中不断请求可以都丢弃,停止输入后在进行验证。 再比如下拉刷新,如何第一次请求的时候执行,多次重复请求的话,直接丢弃掉,停止操作后再进行请求。像这种刷新需要立即请求的情况上面的函数就满足不了了,然后就有了下面的函数
1 | /** |
1 | function testFn () { |
1.当immediate 为true时,立即执行fn,wait时间间隔内频繁调用,不断的重置定时器,并不会触发fn,wait时间之后通过将定时器清空,等待wait间隔之后再次调用,则立即执行fn。之后如此循环往复。
使用场景:比如微博下拉刷新,首次下拉请求,中间频繁的下拉并不会触发请求,一定时间后再刷新会重新触发
2.当immediate 为false,不会立即执行该函数,wait 时间间隔内频繁调用,不断重置定时器,wait 时间间隔后,如果无触发,则清空定时器,触发fn,只执行一次fn;
使用场景:用户输入验证,不在输入过程中处理,停止输入后进行验证