节流函数(throttle)
上一篇文章说了防抖函数的实现,其实着两个函数是类似的,都可以实现性能优化,那么如何抉择呢?
它们的区别是:假设有个时间量为wait,为了具体好理解一点就设wait = 1s
防抖函数是在单位时间(>1)随便触发多少次事件,只要这次触发的时间与上次触发的时间只差不到1s,都不执行;
节流函数是在单位时间(>1)随便触发多少次事件,只要过了1s,就会执行一次。
节流函数的实现
应用场景:现在的需求是这样的,用户在输入框内疯狂输入字符串,每经过1s就向后台发起请求查询数据库。
节流函数有两种实现的方式:一种是定时器,另外一种是计算时间差
定时器实现
function throttle(func, wait) {
let timeout
return function() {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null
func.call(this, arguments)
}, wait)
}
}
}
这就实现了一个节流函数。当触发事件的时候,如果没有设置执行回调函数的定时器,就创建一个。当定时器中的回调函数被执行的时候, 将指向定时器引用的timeout
设置为null
,那么下一此再执行时,又会重新触发并设置一个定时器。
使用定时器创建的节流函数特点是,在时间触发结束后,也会执行一次事件。
计算时间差的实现
function throttle(func, wait) {
let previous = 0
return function() {
let now = +new Date()
let remain = wait - (now - previous)
if (remain < 0) {
previous = now
func.call(this, arguments)
}
}
}
这种实现方式,是将previous(上一次的执行时间戳)
设置为自由变量,然后在每一次触发事件的时候都获取当前时间戳
。remain
的得来可能有点难以理解,将wait
理解为理论等待时间,now - previous
理解为实际等待时间。那么remain
就是剩余时间。如果剩余时间小于0了,那肯定就要执行函数了。在执行之前先将当前执行时间更新保存到previous中。
计算时间差的实现方式的特性为:在第一次触发事件时,回调函数会立即执行;不触发事件后,也不会再执行一次。
更强大的节流函数
最新的需求是这样的,事件第一次触发的时候我要执行该函数,事件停止后,也要再触发一次函数。
function throttle(func, wait, options = {}) {
let timeout,
previous = 0
return function() {
let now = +new Date()
let remain = wait - (now - previous)
if (remain < 0) {
if (previous === 0 && !options.begin) {
previous = now
return
}
if (timeout) {
clearTimeout(timeout)
timeout = null
}
previous = now
func.call(this, arguments)
} else if (!timeout && options.end) {
timeout = setTimeout(() => {
func.call(this, arguments)
timeout = null
}, wait)
}
}
}
这样就完成了强大的节流函数了, 第一次来的时候,可以执行一次回调函数,结束的时候也会执行一次回调函数。并且可以通过options
来配置。
如果设置了options.begin === true
就第一触发事件就立刻会执行回调函数。因为我们设置previous
的的初始值为0
,所以如果previous === 0
就表示是第一次触发该事件,与
上options.begin
就可以得出第一次是否执行该回调函数。
如果设置了options.end === true
事件停止触发后也会执行一次该回调函数。其实在事件触发的整个时间内,定时器中的回调函数从来都没有被执行过,只有在事件停止出发后,定时器内的回调函数才被执行。只要设置了options.end
就相当于只是用计算时间的方式来实现节流。