还搞不清楚防抖和节流?

214 阅读5分钟

一、前言

每一个技术或知识点的诞生,都源于现实生活中的实际应用。而防抖(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)
    }
}

图示: 28b860b6bceef54605ac0acddb69969.png 下面通过一个例子来解释防抖~

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)
    }
}

图示: 9d04ca0d621bece4801a05b434b8e2c.png

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是通过一个一个参数的形式)。