一、前言
每一个技术或知识点的诞生,都源于现实生活中的实际应用。而防抖(debounce)和节流(throttle)是为高频事件的优化,可有效降低高频事件的触发次数,减少页面堵塞、卡顿和资源的消耗,相对更加优雅。
二、概念
1. 防抖
1.1 概念:
防抖(debounce),顾名思义,防止抖动。
在一段时间内(delay)频繁触发某一事件(例如,在输入框里输入时,会频繁触发输入框的 input 事件),该事件只执行一次且是最后触发的这一次,这一现象被称作防抖,即重视周期的结果变化(注意:防抖的周期时间是不固定的,每次在单位时间内再次触发便重新计时,所以是不固定的)。
具体代码如下:
// 防抖
// 接收一个方法 fn, 对其包装,使其在指定时间 delay 抖动完成后再触发
function debounce(fn, delay=200) {
let timer = null
return function() {
if (timer) clearTimeout(timer) // 如果在 delay 时间内再次触发的,则**重新计时**
timer = setTimeout(()=> { // 使用了 ES6 的箭头函数,因为其上下文指向父级
fn.apply(this, arguments) // 这种方式缺陷:无法返回 fn 的 return 值
clearTimeout(timer)
timer = undefined
}, delay)
}
}
图示:
下面通过一个例子来解释防抖~
1.2 结合实例
监控输入框的输入,触发实时监听输入 input 事件的过程
<template>
<div>
<el-input v-model='inputValue' placeholder='请输入' @input='inputChange' />
</div>
</template>
<script setup>
import { ref } from 'vue'
const inputValue = ref()
const inputChange = (val)=> {
console.log('inputChange:', val)
}
</script>
上述代码会去频繁触发 input 事件,但是实际应用中无需每次输入一个字符便触发一次,可以规定一个时间 delay,在这个时间内只触发一次即可,如果在 delay 时间内再次触发,则重新计时,直到 delay 时间内没有输入即结束当前一轮的输入,执行一次触发的事件,这样便可避免部分的消耗(抖动吧,抖动完再触发 input 事件)。
<template>
<div>
<el-input v-model='inputValue' placeholder='请输入' @input='debounceInputChange' />
</div>
</template>
<script setup>
import { ref } from 'vue'
import { debounce } from './utils'
const inputValue = ref()
const inputChange = (val)=> {
console.log('inputChange:', val)
}
// 加上抖动
const debounceInputChange = debounce(inputChange)
</script>
给触发的事件加上抖动,再次在输入框频繁输入,监控 input 事件的触发情况(会发现,触发的次数明显少了许多)。
2. 节流
2.1 概念
节流(throttle),在频繁触发的事件过程中,在单位时间内只执行一次,若在单位时间内再次触发,则退出,不去打扰进入定时器执行的周期,从而减少执行次数,即重视周期变化的过程变化(注意:节流的周期时间是固定的,在这单位时间内,只要没有人为干预取消定时器的执行,必要执行一次,或先执行后计时,或先计时后执行)。
// 节流
// 接收一个方法 fn, 对其包装,使其在单位时间 delay 内只执行一次
function throttle(fn, delay=200) {
let timer = null
return function() {
if (timer) return // 如果在 delay 时间内再次触发的,退出,不执行
timer = setTimeout(()=> { // 使用了 ES6 的箭头函数,因为其上下文指向父级,继承而来
fn.apply(this, arguments) // 这种方式缺陷:无法返回 fn 的 return 值
clearTimeout(timer)
timer = undefined
}, delay)
}
}
图示:
2.2 结合实例
经常在后台项目的开发中遇到,随着屏幕宽度的变化,页面呈现出不一样的布局结构,这里就使用到了节流。在我们的手动变化平铺的宽度,会触发 resize 事件。
三、区别
3.1 代码
- 防抖:如果在规定时间 delay 内再次触发,则重新计时(可能在很长时间内只执行最后一次)
if (timer) clearTimeout(timer)
- 节流:如果在单位时间 delay 内再次触发,则退出
if (timer) return
3.2 触发次数
- 防抖:可能在很长时间内只执行最后一次;
- 节流:与防抖相比,在这段很长时间内,以 delay 为单位时间,每个单位时间执行一次,可能执行多次;
四、应用场景
在两者应用场景的选择上,可根据实际需求需要择选最适合的方式,注意两点:
(1)在执行次数上:防抖执行只执行一次且是最后一次,节流每单位时间执行一次,=;
(2)如果觉得两者都合适的情况,就做一种假设,设置一个单位时间 delay,假设用户在 delay 内一直不停的操作,使用防抖或使用节流,会对当前的实际需求效果上造成什么样的影响,从而选择最合适的。
例如:在写这篇文章时保存方式,若使用防抖,假设我一直在输入(在设置的单位时间内),最后只会保存一次,如果中间网络断开或电脑异常等情况出现,前面写的数据都将丢失,因为没有保存到掘金的数据库,这种情况下,显然选择节流方式更合适,因为每单位时间执行一次保存到数据库的操作,达到实时保存的效果,即使中间出现异常也不会造成太大的损失。
4.1 防抖
防抖用于”不看过程,只看结果“的场景中,例如:
- 下拉搜索输入框,随着输入值的变化,显示匹配到的数据;
4.2 节流
节流用于”看过程变化“的优化场景中,例如:
- 监听页面滚动,触发加载更多;
- 实时保存类的,例如掘金写文章时,边写边保存;
- 监听 resize;
五、总结
- 防抖和节流,是对高频触发事件的优化。防抖在于在抖动完成后再触发,节流是在单位时间内必执行一次。相比而言,在同一时间的抖动过程中,两者的执行次数可能不同;
- 防抖和节流的核心是借助定时器的延时执行,各自以不同的方式延时;
- 在前面防抖和节流函数代码中,使用了箭头函数(箭头函数本身没有执行上下文,其上下文是继承过来的)、apply(更改 this 指向)、arguments(类数组对象,以数组形式存储 funciton 接收的所有参数);
- apply 和 call 都可以改变 this 的指向,但传递的参数形式不同(apply 是通过数组的形式,call是通过一个一个参数的形式)。