折叠何宝莹

throttle 和 debounce 的原理和实现

近期面试遇到了这道题:

1
2
3
4
5
6
7
8
9
10

//评测题目: throttle的简单实现
// throttle的简单实现

function throttle(func, duration) {
// 在这里编写具体实现

}

window.addEventListener('scroll', throttle(func, 50), false);

我:
第一反应是没听过这个,然后立刻网上搜相关信息,想起高程有讲过相关内容,立刻拿出红宝书出来镇场子。

throttle 和 debounce 的原理

先讲讲 throttle 的来源,俗称节流,一般用在滚动场景里,控制时间响应的次数。

高程里面讲函数节流背后的基本思想是指,某些代码不可以在没有间断的情况连续重复执行。第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,它会清除前一次的定时器并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。目的是只有在执行函数的请求停止了一段时间之后才执行。

其实高程里面讲的节流原理是 debounce,实际的代码也是 debounce 的实现代码

这是高程的代码

1
2
3
4
5
6
function throttle (method, context, duration) {
clearTimeout(method.tId)
method.tId = setTimeout(function() {
method.call(context)
}, duration)
}

面试官:

高程的代码实现了 debounce,一开始网上找的答案才是实现 throttle 的。

之前看过别人讲的例子很好理解:

电梯超时
想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应。假设电梯有两种运行策略 throttle 和 debounce ,超时设定为15秒,不考虑容量限制。
throttle 策略的电梯。保证如果电梯第一个人进来后,15秒后准时运送一次,不等待。如果没有人,则待机。
debounce 策略的电梯。如果电梯里有人进来,等待15秒。如果又人进来,15秒等待重新计时,直到15秒超时,开始运送。

具体 JS 函数实现

我:
贴一下网上找的 throttle 的实现

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
var throttle = function (fn, interval) {

// 记录前一次时间
var last = +new Date()

var timerId = null
// 包装完后返回 闭包函数
return function () {
var current = +new Date()
var args = [].slice.call(arguments, 0)
var context = this
// 首先清除定时器
clearTimeout(timerId)
// current 与last 间隔大于interval 执行一次fn
// 在一个周期内 last相对固定 current一直再增加
// 这里可以保证调用很密集的情况下 current和last 必须是相隔interval 才会调用fn
if (current - last >= interval) {
fn.apply(context, args)
last = current
} else {
// 如果没有大于间隔 添加定时器
// 这可以保证 即使后面没有再次触发 fn也会在规定的interval后被调用
timerId = setTimeout(function() {
fn.apply(context, args)
last = current
}, interval)
}
}
}

附上 debounce 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var debounce = function (fn, interval) {

// debounce中的interval 和 throttle中的 interval含义不一样
// 在debounce中可以可以把interval理解成 用户停止了某个连续的操作后 再推迟interval执行fn
var timerId = null

return function () {
var current = +new Date()
var args = [].slice.call(arguments, 0)
var context = this
// 如果调用很密集 可以保证fn永远不会触发 必须等到有前后两个调用的间隔大于等于interval fn才能被执行
// 如果调用很少 fn会在interval结束后被执行
clearTimeout(timerId)
timerId = setTimeout(function() {
fn.apply(context, args)
}, interval)

}
}