-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
【进阶 7-2 期】深入浅出防抖函数 debounce #39
Comments
|
关于加强版throttle。 function throttle(fn, wait = 200) {
let previous = 0
let timer = null
return function(...args) {
let now = Date.now()
if (now - previous > wait) {
// ------ 新增部分 start ------
// 取消上个wait时间内,等待触发(但是过时)的回调。
if (timer) {
clearTimeout(timer)
timer = null
}
// ------ 新增部分 end------
previous = now
fn.apply(this, args)
} else {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
clearTimeout(timer)
timer = null
fn.apply(this, args)
}, wait)
}
}
} |
我认为加强版 throttle 那里有个小问题: // 我拷出来放到编辑器格式化过的,并移除相关的注释(强迫症)
function throttle(fn, wait) {
let previous = 0
let timer = null
return function (...args) {
let now = Number(new Date())
// 新增
if (timer) clearTimeout(timer) // <--------------- 应该移到到这里
if (now - previous < wait) {
// 1️⃣
// if (timer) clearTimeout(timer) // <---------- 原先的位置,应移到到外面一层
timer = setTimeout(() => {
previous = now
fn.apply(this, args)
}, wait)
} else {
// 2️⃣
previous = now
fn.apply(this, args)
}
}
} 考虑一种情况: 假设我在 4s 的时候,触发了一次,这时应该走 2️⃣ 逻辑。然后在 4.9s 的时候,又触发了一次这时候走的 1️⃣ 逻辑。然后时间到了 5s 触发了一次(后面就停止操作了),它会走 2️⃣ 逻辑一次,接着时间来的 5.9s,它还会执行一遍 fn.apply(this, args),因为在 5s 触发时,没有 当然上面举例都是比较理想的状态,实际场景可能几乎碰不到,但从严谨触发,应该在每次触发的时候都清除一下定时器。 |
引言
上一节我们认识了节流函数 throttle,了解了它的定义、实现原理以及在 underscore 中的实现。这一小节会继续之前的篇幅聊聊防抖函数 debounce,结构是一样的,将分别介绍定义、实现原理并给出了 2 种实现代码并在最后介绍在 underscore 中的实现,欢迎大家拍砖。
有什么想法或者意见都可以在评论区留言,下图是本文的思维导图,高清思维导图和更多文章请看我的 Github。
定义及解读
防抖函数 debounce 指的是某个函数在某段时间内,无论触发了多少次回调,都只执行最后一次。假如我们设置了一个等待时间 3 秒的函数,在这 3 秒内如果遇到函数调用请求就重新计时 3 秒,直至新的 3 秒内没有函数调用请求,此时执行函数,不然就以此类推重新计时。
举一个小例子:假定在做公交车时,司机需等待最后一个人进入后再关门,每次新进一个人,司机就会把计时器清零并重新开始计时,重新等待 1 分钟再关门,如果后续 1 分钟内都没有乘客上车,司机会认为乘客都上来了,将关门发车。
此时「上车的乘客」就是我们频繁操作事件而不断涌入的回调任务;「1 分钟」就是计时器,它是司机决定「关门」的依据,如果有新的「乘客」上车,将清零并重新计时;「关门」就是最后需要执行的函数。
如果你还无法理解,看下面这张图就清晰多了,另外点击 这个页面 查看节流和防抖的可视化比较。其中 Regular 是不做任何处理的情况,throttle 是函数节流之后的结果(上一小节已介绍),debounce 是函数防抖之后的结果。
原理及实现
实现原理就是利用定时器,函数第一次执行时设定一个定时器,之后调用时发现已经设定过定时器就清空之前的定时器,并重新设定一个新的定时器,如果存在没有被清空的定时器,当定时器计时结束后触发函数执行。
实现 1
实现 2
上述实现方案已经可以解决大部分使用场景了,不过想要实现第一次触发回调事件就执行 fn 有点力不从心了,这时候我们来改写下 debounce 函数,加上第一次触发立即执行的功能。
实现原理比较简单,判断传入的 immediate 是否为 true,另外需要额外判断是否是第一次执行防抖函数,判断依旧就是 timer 是否为空,所以只要
immediate && !timer
返回 true 就执行 fn 函数,即fn.apply(this, args)
。加强版 throttle
现在考虑一种情况,如果用户的操作非常频繁,不等设置的延迟时间结束就进行下次操作,会频繁的清除计时器并重新生成,所以函数 fn 一直都没办法执行,导致用户操作迟迟得不到响应。
有一种思想是将「节流」和「防抖」合二为一,变成加强版的节流函数,关键点在于「 wait 时间内,可以重新生成定时器,但只要 wait 的时间到了,必须给用户一个响应」。这种合体思路恰好可以解决上面提出的问题。
给出合二为一的代码之前先来回顾下 throttle 函数,上一小节中有详细的介绍。
结合 throttle 和 debounce 代码,加强版节流函数 throttle 如下,新增逻辑在于当前触发时间和上次触发的时间差小于时间间隔时,设立一个新的定时器,相当于把 debounce 代码放在了小于时间间隔部分。
看完整段代码会发现这个思想和上篇文章介绍的 underscore 中 throttle 的实现思想非常相似。
underscore 源码解析
看完了上文的基本版代码,感觉还是比较轻松的,现在来学习下 underscore 是如何实现 debounce 函数的,学习一下优秀的思想,直接上代码和注释,本源码解析依赖于 underscore 1.9.1 版本实现。
相比上文的基本版实现,underscore 多了以下几点功能。
小结
函数节流和防抖都是「闭包」、「高阶函数」的应用
函数节流 throttle 指的是某个函数在一定时间间隔内(例如 3 秒)执行一次,在这 3 秒内 无视后来产生的函数调用请求
函数防抖 debounce 指的是某个函数在某段时间内,无论触发了多少次回调,都只执行最后一次
参考
文章穿梭机
❤️ 看完三件事
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
The text was updated successfully, but these errors were encountered: