您的位置:首页 > 其它

Debounce 和 Throttle 的原理及实现

2017-05-02 21:13 134 查看

Debounce 和 Throttle 的原理及实现

当我们在处理诸如
resize
scroll
mousemove
keydown/keyup/keypress
等事件的时候,通常我们不希望这些事件太过频繁地触发,尤其是监听程序中涉及到大量的计算或者有非常耗费资源的操作。如果鼠标连续移动,那么浏览器就应该触发多个连续的
mousemove
事件,这意味着浏览器会在其内部计时器允许的情况下,根据用户移动鼠标的速度来触发
mousemove
事件。

Debounce

DOM 事件里的
debounce
概念其实是从机械开关和继电器的“去弹跳”(debounce) 衍生出来的,基本思路就是把多个信号合并为一个信号。

在 JavaScript 中,
debounce
函数所做的事情就是, 强制一个函数在某个连续时间段内只执行一次,哪怕它本来会被调用多次 。我们希望在用户停止某个操作一段时间之后才执行相应的监听函数,而不是在用户操作的过程当中,浏览器触发多少次事件,就执行多少次监听函数。比如,在某个 3s 的时间段内连续地移动了鼠标,浏览器可能会触发几十(甚至几百)个
mousemove
事件,不使用
debounce
的话,监听函数就要执行这么多次;如果对监听函数使用 100ms 的“去弹跳”,那么浏览器只会执行一次这个监听函数,而且是在第 3.1s 的时候执行的。

现在,我们就来实现一个
debounce
函数。

/**
* @param fn {Function}   实际要执行的函数
* @param delay {Number}  延迟时间,也就是阈值,单位是毫秒(ms)
* @return {Function}     返回一个“去弹跳”了的函数
*/
function debounce(fn, delay) {
var timer
// 返回一个函数,这个函数会在一个时间区间结束后的 delay 毫秒时执行 fn 函数
return function () {
// 保存函数调用时的上下文和参数,传递给 fn
var context = this
var args = arguments
// 每次这个返回的函数被调用,就清除定时器,以保证不执行 fn
clearTimeout(timer)
// 当返回的函数被最后一次调用后(也就是用户停止了某个连续的操作),
// 再过 delay 毫秒就执行 fn
timer = setTimeout(function () {
fn.apply(context, args)
}, delay)
}
}


其实思路很简单, debounce 返回了一个闭包,这个闭包依然会被连续频繁地调用,但是在闭包内部,却限制了原始函数 fn 的执行,强制 fn 只在连续操作停止后只执行一次。
debounce 的使用方式如下:

$(document).on('mouvemove', debounce(function(e) {

}, 200))


我们在处理防止用户连续快速点击按钮时就可以采用这种方法解决。

Throttle

throttle 的概念理解起来更容易,就是 固定函数执行的速率 ,即所谓的“节流”。正常情况下, mousemove 的监听函数可能会每 20ms(假设)执行一次,如果设置 200ms 的“节流”,那么它就会 每 200ms 执行一次。比如在 1s 的时间段内,正常的监听函数可能会执行 50(1000/20) 次,“节流” 200ms 后则会执行 5(1000/200) 次。

/**
*
* @param fn {Function}   实际要执行的函数
* @param delay {Number}  执行间隔,单位是毫秒(ms)
* @return {Function}     返回一个“节流”函数
*/

function throttle(fn, threshhold) {
// 记录上次执行的时间
var last
var timer

// 默认间隔为 250ms
threshhold || (threshhold = 250)

// 返回的函数,每过 threshhold 毫秒就执行一次 fn 函数
return function () {
// 保存函数调用时的上下文和参数,传递给 fn
var context = this
var args = arguments
var now = +new Date()
// 如果距离上次执行 fn 函数的时间小于 threshhold,那么就放弃
// 执行 fn,并重新计时
if (last && now < last + threshhold) {
clearTimeout(timer)

// 保证在当前时间区间结束后,再执行一次 fn
timer = setTimeout(function () {
last = now
fn.apply(context, args)
}, threshhold)

// 在时间区间的最开始和到达指定间隔的时候执行一次 fn
} else {
last = now
fn.apply(context, args)
}
}
}


原理也不复杂,相比
debounce
,无非是多了一个时间间隔的判断,其他的逻辑基本一致。
throttle
的使用方式和
debounce一样。


总结

debounce
强制函数在某段时间内只执行一次,
throttle
强制函数以固定的速率执行。在处理一些高频率触发的 DOM 事件的时候,它们都能极大提高用户体验。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: