前言
在日常开发中经常会遇到这样的应用场景:需要判断滚轮是否滚到最底部以进行滚动加载,处理输入框中的输入内容,以及监听浏览器窗口大小等操作。这些操作通常会频繁连续地触发相应的回调函数,从而浪费资源并降低前端性能。
为了优化体验、降低资源消耗、提升性能,也为了防止被测试同学暴捶,我们还是还是有必要在日常开发中注意使用防抖和节流的。
介绍
首先,让我们来讨论一下防抖和节流的概念:
在之前提到的高频率触发回调函数的情况下,为了限制这些事件的调用次数,我们可以采用防抖(debounce)和节流(throttle)的方法来降低调用频率。
定义:
- 节流:在n秒内只执行一次,若在n秒内多次触发,则只有一次生效。
- 防抖:n秒后执行该事件,若在n秒内再次触发,则重新计时。
一个经典的比喻是想象每天上班时在大厦底下等候电梯。将电梯完成一次运送比作为一次函数的执行和响应。
假设电梯有两种运行策略:debounce
和throttle
,超时设定为15秒,不考虑容量限制。
- 当第一个人进入电梯后,15秒后准时运送一次,这就是节流。
- 当第一个人进入电梯后,等待15秒。如果期间有更多人进入,15秒等待时间会重新计时,直到15秒后开始运送,这就是防抖。
代码实现参考
防抖
function throttled(fn, delay) {
let timer = null
let starttime = Date.now()
return function () {
let curTime = Date.now() // 当前时间
let remaining = delay - (curTime - starttime) // 从上一次到现在,还剩下多少多余时间
let context = this
let args = arguments
clearTimeout(timer)
if (remaining <= 0) {
fn.apply(context, args)
starttime = Date.now()
} else {
timer = setTimeout(fn, remaining);
}
}
}
节流
function debounce(func, wait, immediate) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout); // timeout 不为null
if (immediate) {
let callNow = !timeout; // 第一次会立即执行,以后只有事件执行后才会再次触发
timeout = setTimeout(function () {
timeout = null;
}, wait)
if (callNow) {
func.apply(context, args)
}
}
else {
timeout = setTimeout(function () {
func.apply(context, args)
}, wait);
}
}
}
区别
相同点:
- 都可以通过使用
setTimeout
实现 - 目的都是降低回调执行频率,以节省计算资源
不同点:
- 函数防抖会在一段连续操作结束后处理回调,利用
clearTimeout
和setTimeout
实现。函数节流在一段连续操作中,每段时间只执行一次,适用于频率较高的事件以提高性能。 - 函数防抖关注一定时间内连续触发的事件,只在最后执行一次;而函数节流在一段时间内只执行一次。
例如,若将时间频率设置为500ms,在2秒内频繁触发函数时,节流会每隔500ms执行一次。而防抖则在2秒后,无论调用多少次方法,只会执行一次。
应用场景
防抖适用于连续事件中只需触发一次回调的场景,例如:
- 搜索框输入搜索。只在用户最后一次输入完成后发送请求。
- 手机号、邮箱验证输入检测。
- 窗口大小调整
resize
。只在窗口调整完成后计算窗口大小,避免重复渲染。
节流适用于间隔一段时间执行一次回调的场景,例如:
- 滚动加载,加载更多或滚动到底部监听。
- 搜索框,搜索联想功能。
结语
至此,JS的防抖和节流就已经介绍完了。
欢迎大家留言讨论,不足之处也烦请批评指正。