防抖和节流是针对响应跟不上触发频率这类问题的两种解决方案。应用场景有很多,输入框持续输入,将输入内容远程校验、多次触发点击事件、onScroll等等。 处理不当或者放任不管就容易引起浏览器卡死。
针对快速连续触发和不可控的高频触发问题, 给出debounce 和 throttling了两种解决策略;
防抖(debounce)—
策略是当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。 这是debounce的基本思想,在后期又扩展了前缘debounce,即执行动作在前,然后设定周期,周期内有事件被触发,不执行动作,且周期重新设定。
延迟debounce
前缘debounce
debounce的特点是当事件快速连续不断触发时,动作只会执行一次。 延迟debounce,是在周期结束时执行,前缘debounce,是在周期开始时执行。但当触发有间断,且间断大于我们设定的时间间隔时,动作就会有多次执行。
效果:如果短时间内大量触发同一事件,只会执行一次函数。
实现:既然前面都提到了计时,那实现的关键就在于setTimeout这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| /* * fn [function] 需要防抖的函数 * delay [number] 毫秒,防抖期限值 */ function debounce(fn,delay){ let timer = null //借助闭包 return function() { if(timer){ clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时 timer = setTimeout(fn,delay) }else{ timer = setTimeout(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时 } } }
|
稍微简化下:
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
| /*****************************简化后的分割线 ******************************/ function debounce(fn,delay){ let timer = null //借助闭包 return function() { if(timer){ clearTimeout(timer) } timer = setTimeout(fn,delay) // 简化写法 } } // 然后是旧代码 function showTop () { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log('滚动条位置:' + scrollTop); } window.onscroll = debounce(showTop,1000) // 为了方便观察效果我们取个大点的间断值,实际使用根据需要来配置 ```` 此时会发现,必须在停止滚动1秒以后,才会打印出滚动条位置。
## 节流(throttle)--- 策略是,让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。 节流策略也分前缘和延迟两种。与debounce类似,延迟是指 周期结束后执行动作,前缘是指执行动作后再开始周期。
`延迟throttling`:
![延迟throttling](https://images.nirvana.net.cn/static/images/images/10024246-170a946907e707c2?OSSAccessKeyId=TMP.3KeXZGnENvALyisAJT1gqw2AwdbuZ5LxbUySwqJ1HBKf4H4rWRweoArKyPtgRGs68tijz3gt5FE7Z511u6uMb2HP6HmqhN&Signature=f2DPWFzGx%2BU%2BELJYUqFfcGJRLgQ%3D)
前缘throttling :
![前缘throttling ](https://images.nirvana.net.cn/static/images/images/10024246-1f3724b815810fcf?OSSAccessKeyId=TMP.3KeXZGnENvALyisAJT1gqw2AwdbuZ5LxbUySwqJ1HBKf4H4rWRweoArKyPtgRGs68tijz3gt5FE7Z511u6uMb2HP6HmqhN&Signature=f2DPWFzGx%2BU%2BELJYUqFfcGJRLgQ%3D)
throttling的特点在连续高频触发事件时,动作会被定期执行,响应平滑。 **效果:**如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。
**实现:** 这里借助setTimeout来做一个简单的实现,加上一个状态位valid来表示当前函数是否处于工作状态。
|
function throttle(fn,delay){
let run = true
return function () {
if (!run) {
return // 如果开关关闭了,那就直接不执行下边的代码
}
run = false // 持续触发的话,run一直是false,就会停在上边的判断那里
setTimeout(() => {
func.apply(this, arguments)
run = true // 定时器到时间之后,会把开关打开,我们的函数就会被执行
}, delay)
}
}
/* 请注意,节流函数并不止上面这种实现方案,
例如可以完全不借助setTimeout,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。
也可以直接将setTimeout的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行fn之后消除定时器表示激活,原理都一样
*/
// 以下照旧
function showTop () {
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log(‘滚动条位置:’ + scrollTop);
}
window.onscroll = throttle(showTop,1000)
## 总结
防抖和节流巧妙地用了setTimeout,来控制函数执行的时机,优点很明显,可以节约性能,不至于多次触发复杂的业务逻辑而造成页面卡顿。复杂情况可参考underscope.js的[throttle](https://underscorejs.net/#throttle)和[debounce](https://underscorejs.net/#debounce)