debounce-and-throttle

平时 coding 的时候总是会用到这两个函数,但是一直没有仔细研究过它们俩到底有什么区别,最近抽空学习了一下 lodash 中这两个函数的源码,以下是这次学习的总结。

Debounce

debounce 创建了一个在上一次函数执行结束后等待一定时间,之后才执行的防反跳函数。
以电梯为例,电梯设置了等待 10秒 运行,这时候进来一个人,那么按照 debounce 的逻辑,需再等 10 秒,电梯才会运行。
在工作中,曾经遇到一个需求就是监测用户键入,当用户键入停止就保存草稿。
如果不用 debounce, 那么每次 keyup 都会发起一次请求,这样会大大增加服务器的压力;使用了 debounce, 设定 wait 3 秒,那么在用户停止键入之后,等待 3 秒,如果用户没有继续键入,就保存草稿,如果再次键入,那么就继续等待。这样就节省了大量的请求,也就减轻了服务器的压力。

在 lodash 中,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
30
31
32
33
34
35
36
37
38
// 参数:func 需要使用debounce防止多次短时间多次执行的函数
// 参数: wait 最大等待时间
// 参数:options对象,有leading、trailing和maxWait参数。
// leading, 默认为false,表示首次调用的时候需要执行函数;
// trailing, 默认为true,表示等待特定时间之后执行
// 有一应用场景,就是设置leading:true; trailng: fals,用于发送请求的时候防止连续点击。
// 方法:cancel 用于取消限制执行次数
// 方法:flush 立即执行
function debounced(func, wait, { leading: false, trailing }) {
const time = Date.now()
// 根据时间判断是否已经可以执行函数
const isInvoking = shouldInvoke(time)
lastArgs = args
lastThis = this
lastCallTime = time
if (isInvoking) {
// 如果还没有定时器,那就是第一次触发事件,通过leading参数判断第一次触发时需不需要执行
if (timerId === undefined) {
return leadingEdge(lastCallTime)
}
// 有参数maxWait, maxing由maxWait计算得来,这里省略
// 如果已经过了最大等待时间,那么执行函数,并且重新定时
if (maxing) {
// Handle invocations in a tight loop.
timerId = setTimeout(timerExpired, wait)
return invokeFunc(lastCallTime)
}
}
if (timerId === undefined) {
timerId = setTimeout(timerExpired, wait)
}
return result
}
debounced.cancel = cancel
debounced.flush = flush

Throttle

throttle 是节流的意思,throttle 函数也是用阻止函数在短时间内多次调用。具体实现,就是在当前函数执行之后,等待特定时间之后再次执行,不管中间函数被调用多少次。
还是以电梯为例,电梯设定 10 秒运行一次。那么到达 10 秒,电梯就会马上运行,如果这时候有人要进,也不会等待。

在平时工作中,也有应用场景,如鼠标拖拽。一般拖拽的时候会要求 拖拽的元素一直跟随鼠标上下左右动。这里就会涉及大量的 DOM 渲染,而我们知道 DOM 渲染是极耗性能的,
很可能会导致浏览器卡顿,在 IE 中甚至会导致浏览器崩溃。使用 throttle,就可以在用户可接受的时间间隔内限制事件的触发次数,极大的减少性能的消耗。
下面我们看一下 lodash 中 throttle 的实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function throttle(func, wait, options) {
let leading = true
let trailing = true
if (typeof func != 'function') {
throw new TypeError('Expected a function')
}
if (isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading
trailing = 'trailing' in options ? !!options.trailing : trailing
}
return debounce(func, wait, {
'leading': leading,
'maxWait': wait,
'trailing': trailing
})
}

可以发现,其实在 lodash 中,throttle 就是 maxWait 等于 wait,并且 leading 为 true 的 debounce 函数的特例。同样的,throttle 也有 cancel 和 flush 方法。

参考:
http://www.alloyteam.com/2012/11/javascript-throttle/
JS 高级程序设计