这个问题很直接,一上来就问到了关键问题!那就顺着这条线去看一下,什么是防抖节流,有哪些应用场景?
什么是防抖,为什么要防抖,怎么实现,有哪些应用场景?
什么是防抖?
关于什么是防抖,从字面意思看,就是防止手抖。假设现在有一个手抖的厉害的用户,要点击一个按钮。他的本意是点一下就可以了,但是由于手抖的缘故,哒哒哒连续点了好几下。
这个时候,如果什么都不做,按钮后面绑定的事件就会调用对应的次数,性能上有问题。如果每次调用还伴随一个弹框,会导致用户体验很差。所以基于用户手抖的假设,我们采取策略,在他确定点击完了,然后调用一次即可。这样不光用户体验好,我们后台程序也能减少消耗。
那怎么确定用户点击结束了呢?面对客户的只有页面,其实是没办法知道的,唯一能做的就是假设一个动作在一段时间内不再出现,表明用户知道自己已经点完了,在安静的等待反馈,这个时候就可以调用对应的函数了。所以,每次点击结束,都会等等看,直到过了单位时间,才会去调用。
防抖就是高频事件后面绑定的函数在单位事件内只能执行一次,如果事件在单位时间还没到的时候被触发,单位时间则重置为 0 重新开始计算。
怎么实现防抖?
既然已经知道什么是防抖了,剩下的自然是实现。
首先,需要确定单位时间,这里为了便于观察,将单位时间设置为 5 秒,正常开发中 1 秒居多。
有了单位时间后,剩下的就是需要一个变量startTime
来存储上一次触发时间,然后和当前时间比对,如果时间不到,则取消正在等待执行的函数,重新计时。
最后就是需要一个待执行的处理函数 setTimeout
代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<button onClick="debunceFunc()">我是防抖按钮</button>
<script>
let startTime = 0
let timeId = null
function debunceFunc() {
const now = Date.now()
if ((now - startTime1) / 1000 < 5) {
clearTimeout(timeId)
}
startTime1 = now
timeId = setTimeout(() => {
alert('点我了www')
}, 5000)
}
</script>
一个简单的防抖小程序就实现了。如果你拿着秒表去点击按钮,每次快到 5 秒的时候点击一下,就可以将弹框的弹出时间延迟 5 秒,直到不再点击。
但是这里也有一个问题,就是记录时间和 timeid 用的是全局变量,很容易造成变量污染,出现 bug。所以为了能让防抖函数变得更加的易用,可以将它封装成通用的。
要想将防抖函数封装成通用的,最好的变法就是做一个包装器,不管什么函数来了,都给你改造成防抖函数。
- 先定义一个函数,接受另外一个需要改造的函数和单位时间两个参数
1 | /** |
- 返回一个函数,透传接受到的参数
1
2
3
4
5
6
7
8
9/**
* @param {function} func - 需要改造的函数
* @param {number} time - 需要等待的时间
*/
function wrapperDebouce (func,time) {
return function (argument) {
}
} 将 startTime 和 timeId 放入闭包
1
2
3
4
5
6
7
8
9
10
11/**
* @param {function} func - 需要改造的函数
* @param {number} time - 需要等待的时间
*/
function wrapperDebouce (func,time) {
let startTime = 0
let timeId = null
return function (argument) {
}
}移植防抖基础代码,如果没到取消等待执行的事件,然后重新开始待执行事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* @param {function} func - 需要改造的函数
* @param {number} time - 需要等待的时间
*/
function wrapperDebouce (func,time) {
let startTime = 0
let timeId = null
return function (arguments) {
const now = Date.now()
if ((now - startTime) / 1000 < 5) {
clearTimeout(timeId)
}
startTime = now
timeId = setTimeout(func.call(this,...arguments))
}
}使用包装器
1 | /** |
防抖的使用场景
基于现有的认识,一些需要时间的交互应该都需要防抖。比如各种通过和用户交互获取后台服务请求,包括获取验证码,登录,懒加载等。
什么是节流?
顾名思义,节流就是节省流量的意思。为什么要节留呢?因为这个流量一直在走,基于现实的需要,不可能让它停下来,只能是想办法减少消耗。因此,节流就是将高频发生的事件频率降低,在不管不顾的情况下,单位时间内可能会执行 n 次,节流后,在不影响用户体验的情况下,可能单位时间内执行一次就行了。
怎么实现节流?
既然已经知道什么是节流了,剩下的自然是实现。
假设现在有一个人现在想抢某明星演唱会的门票,会反复点击按钮。这个时候如果使用防抖策略,显然是不对的,本来多点几次是为了提高抢中的概率,结果越点触发时间越晚,真是会哭。
所以,为了他好服务器也好,只能采取单位时间执行一次的策略。为了更加直观的观察,把这个单位时间设置的长一点,5秒。
有了单位时间后,要做的第二件事就是确定有没有到 5秒,需要一个变量来存储时间,然后使用 Date.now()来获取事件戳。代码修改后如下
1 | <button onClick="throttle()">我是节流按钮,已经防抖</button> |
这样,一个简单的节流程序就实现了。说句题外话,开始关注时间的时候,30 秒,10 秒,5 秒都真的很长。我一开始想的是把单位时间设置成 30 秒,结果等了很久才出现下一次 console,一度让我怀疑代码有问题,后来十秒,依旧觉得长,5 秒也是这样。
虽然已经实现了一个具有节流功能的小程序,但是这样的程序,不具备复用性,怎么才能让它具有复用型呢?
答案是做一个包装函数,来将普通函数防抖化。
在 JavaScript中,函数可以当做变量来传递,所以在做包装器的时候,只需要把传入进来的函数当做一个变量即可。在包装函数中,除了接收一个函数外,还有一个可选参数就是单位时间。作为一个公用函数,肯定是要给用户自主权,还要给他们便利,所以单位时间设置成可选参数,给它一个默认值
1 | /** |
使用场景
节流适用于那些需要一直有反馈,且反馈随时都在变化的应用场景,除了刚才的抢票外,像一些搜索框,懒加载都可以用。