什么是防抖和节流,他们的应用场景有哪些

这个问题很直接,一上来就问到了关键问题!那就顺着这条线去看一下,什么是防抖节流,有哪些应用场景?

什么是防抖,为什么要防抖,怎么实现,有哪些应用场景?

什么是防抖?

关于什么是防抖,从字面意思看,就是防止手抖。假设现在有一个手抖的厉害的用户,要点击一个按钮。他的本意是点一下就可以了,但是由于手抖的缘故,哒哒哒连续点了好几下。

这个时候,如果什么都不做,按钮后面绑定的事件就会调用对应的次数,性能上有问题。如果每次调用还伴随一个弹框,会导致用户体验很差。所以基于用户手抖的假设,我们采取策略,在他确定点击完了,然后调用一次即可。这样不光用户体验好,我们后台程序也能减少消耗。

那怎么确定用户点击结束了呢?面对客户的只有页面,其实是没办法知道的,唯一能做的就是假设一个动作在一段时间内不再出现,表明用户知道自己已经点完了,在安静的等待反馈,这个时候就可以调用对应的函数了。所以,每次点击结束,都会等等看,直到过了单位时间,才会去调用。

防抖就是高频事件后面绑定的函数在单位事件内只能执行一次,如果事件在单位时间还没到的时候被触发,单位时间则重置为 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
2
3
4
5
6
7
/**
* @param {function} func - 需要改造的函数
* @param {number} time - 需要等待的时间
*/
function wrapperDebouce (func,time) {

}
  • 返回一个函数,透传接受到的参数
    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
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
/**
* @param {function} func - 需要改造的函数
* @param {number} time - 需要等待的时间
*/
<button onClick="debouceWraperF()">加了包装器的按钮</button>
<script>
function wrapperDebouce(func,time) {
let startTime = 0
let timeId = null
return function (arguments) {
const now = Date.now()
if ((now - startTime) / 1000 < time) {
clearTimeout(timeId)
}
startTime = now
if (arguments) {
timeId = setTimeout(() => {
func.call(this, arguments)
}, time * 1000)
} else {
timeId = setTimeout(func, time * 1000)
}
}
}
function needDebouceWraper () {
alert('wrapper debounce')
}
const debouceWraperF = wrapperDebouce(needDebouceWraper, 10)
</script>

防抖的使用场景

基于现有的认识,一些需要时间的交互应该都需要防抖。比如各种通过和用户交互获取后台服务请求,包括获取验证码,登录,懒加载等。

什么是节流?

顾名思义,节流就是节省流量的意思。为什么要节留呢?因为这个流量一直在走,基于现实的需要,不可能让它停下来,只能是想办法减少消耗。因此,节流就是将高频发生的事件频率降低,在不管不顾的情况下,单位时间内可能会执行 n 次,节流后,在不影响用户体验的情况下,可能单位时间内执行一次就行了。

怎么实现节流?

既然已经知道什么是节流了,剩下的自然是实现。

假设现在有一个人现在想抢某明星演唱会的门票,会反复点击按钮。这个时候如果使用防抖策略,显然是不对的,本来多点几次是为了提高抢中的概率,结果越点触发时间越晚,真是会哭。

所以,为了他好服务器也好,只能采取单位时间执行一次的策略。为了更加直观的观察,把这个单位时间设置的长一点,5秒。

有了单位时间后,要做的第二件事就是确定有没有到 5秒,需要一个变量来存储时间,然后使用 Date.now()来获取事件戳。代码修改后如下

1
2
3
4
5
6
7
8
9
10
11
12
<button onClick="throttle()">我是节流按钮,已经防抖</button>
<script>
let startTime = 0
function throttle() {
const now = Date.now()
if ((now - startTime) / 1000 < 5) {
return
}
startTime = now
console.log('点我了')
}
</script>

这样,一个简单的节流程序就实现了。说句题外话,开始关注时间的时候,30 秒,10 秒,5 秒都真的很长。我一开始想的是把单位时间设置成 30 秒,结果等了很久才出现下一次 console,一度让我怀疑代码有问题,后来十秒,依旧觉得长,5 秒也是这样。

虽然已经实现了一个具有节流功能的小程序,但是这样的程序,不具备复用性,怎么才能让它具有复用型呢?
答案是做一个包装函数,来将普通函数防抖化。

在 JavaScript中,函数可以当做变量来传递,所以在做包装器的时候,只需要把传入进来的函数当做一个变量即可。在包装函数中,除了接收一个函数外,还有一个可选参数就是单位时间。作为一个公用函数,肯定是要给用户自主权,还要给他们便利,所以单位时间设置成可选参数,给它一个默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/** 
节流包装函数
@constructor
@param {Function} func - 需要改造的函数
@param {number} time - 防抖单位事件,默认 1 秒
*/
function debuounceWrapper ( func, time = 1) {
let startTime = 0
return function (arguments) {
const now = Date.now()
if ((now -startTime) / 30 , time) {
return
} else {
startTime = now
if (arguments) {
func.call(this, ...arguments)
} else {
func()
}
}
}
}

使用场景

节流适用于那些需要一直有反馈,且反馈随时都在变化的应用场景,除了刚才的抢票外,像一些搜索框,懒加载都可以用。