关于 Throttle 和 Debounce

概述

这两个函数常用于优化方面,功能分别为”将短时间内连续的操作集合成一个,也就是每段时间最多执行一次“与”将短时间内连续的操作只视作一次“。

这个说法有点抽象,可以看个图:

WX20190721-184002@2x.png

操作会新建时间轴,在同一个时间轴内的操作都会被”吸收”,直到时间轴结束才触发,这是节流

每个操作都会新建时间轴,并且替换原有的,直到时间轴结束才触发,这是去抖,也可以理解成有一个公共的时间轴,每次操作都会重置他。

这里的关键就是时间轴会不会被刷新,这两个函数都是高阶函数,接受函数作为参数,同时返回新的函数,要记录下前一个操作,就得使用闭包的特性

节流

1
2
3
4
5
6
7
8
9
10
11
const throttle = (func, threshold = 500) => {
let last = 0
return (...args) => {
const now = new Date()
if (now - last > threshold) {
setTimeout(() => {
func.apply(this, args)
}, threshold)
last = now
}
}

首先记录上一个时间轴的初始时间 last,当与上一次执行的时间超过 500 毫秒时,表示上一个时间轴已经结束,这也就意味着本次操作会创建一个新的时间轴,所以使用 setTimeout 在 500 毫秒后触发一次,之后记录下当前时间轴的初始时间 last。

还可以换一种写法,用定时器的 ID timer 来取代时间:

1
2
3
4
5
6
7
8
9
10
11
12
const throttle = (func, threshold = 500) => {
let timer
return (...args) => {
if (timer) {
return
}
timer = setTimeout(() => {
func.apply(this, args)
timer = null
}, threshold)
}
}

第一次操作先使用 setTimeout 设置时间轴结束后的触发,并保存 ID 至 timer,下一次操作会判断 timer是否存在,存在表示上一个时间轴未结束,不做处理,若不存在则表示上一个时间轴已经结束,可以看作一个新操作进行处理。

去抖

与节流相比,去抖要频繁创建 setTimeout 又要频繁清除之前的 setTimeout,所以必须保留 timer,再加上 last 这个时间变量会更加复杂,所以这里只展示 timer 的写法:

1
2
3
4
5
6
7
8
9
10
const debounce = (func, threshold = 500) => {
let timer
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => {
timer = null
func.apply(this, args)
}, threshold)
}
}

首先使用 timer 记录上一次的定时器 ID,有新操作时先清除定时器,再创建一个新的定时器,直到时间间隔大于 threshold,让定时器执行完成。

总结

还有更优秀的实现,比如 lodash 的,可以让 debounce 在首次操作后就立即触发,并且忽视后续操作。

这里只是用做解析的例子,所以只提供了简单的实现,这两个函数通常会用在降低操作的频繁程度上,比如百度搜索的搜索栏,使用了去抖,并不是每打一个字都会发一次搜索请求的,打字过快时就不会发请求了,直到速度暂缓或者停止时才会继续发请求。

节流也是用在类似的环境下,比如修改 viewport 尺寸会引发重排,重排多了会影响效率,这个时候可以考虑使用节流或去抖,但是去抖会在频繁操作结束后执行,而节流在频繁操作中每隔一段时间就会执行一次,这可以使操作效果更加平滑缓和。