一、前言
防抖和节流是我们前端开发性能优化中经常用到的一个手段,目的就是为了减少/限制函数执行的频率,用来提升系统性能和用户体验。本篇文章将深入探讨防抖和节流这两种函数优化手段,防抖在函数频繁触发时只在最后一次且规定时间内无再次触发才执行,节流则在给定时间间隔内最多执行一次。
有大佬以游戏为喻,将防抖技术形象地比作游戏中的【回城】功能。想象一下,当你按下回城键,角色开始返回基地,但如果在途中被敌人打断,回城动作便会重置,重新开始计算。防抖正是如此,它设定了一个时间窗口,如果在窗口期内再次触发事件,之前的处理便会被取消,重新计时等待下一次有效触发。
而节流技术,则被巧妙地类比为游戏中的【技能触发机制】。每个技能使用后都需要一定的“冷却时间”,在这段时间内,即使你再次按下技能键,也无法立即释放。节流正是通过设定这样的时间间隔,确保函数不会在短时间内被频繁调用,从而避免了资源的过度消耗。
二、防抖(Debounce)
1.防抖的概念
防抖(Debounce)技术是一种常用的性能优化手段。它的概念可以理解为:当某个事件被频繁触发时,防抖技术会确保在一定时间间隔内只执行一次事件处理函数。如果在该时间间隔内事件再次被触发,那么之前的等待将被取消,重新开始计时,直到最后一次触发事件后的指定时间间隔结束,才执行事件处理函数。
用一个简单的示例来理解防抖的概念:你按下电梯按钮,电梯不会立即响应,而是会等待片刻(比如3秒)。如果在这3秒内你又按了一次按钮,等待时间就会重新计算。只有当这3秒内没有新的按键动作,电梯才会开始运行。
2.应用场景
以下是防抖常用的3种应用场景:
- 输入框搜索建议:当用户输入时,防抖可以用于延迟发送请求,避免频繁请求后端接口。
- 窗口大小调整:当窗口大小调整时,防抖可以用于调整事件触发的频率,避免频繁操作导致页面抖动。
- 按钮防重复点击:当用户频繁点击按钮时,防抖可以用于限制按钮点击的频率,避免重复提交操作。
下面以「按钮防重复点击」为例演示一下使用防抖功能和未使用防抖功能的对比,通过图1未设置防抖可以看出每次点击按钮都会执行操作,图2频繁点击则只会执行最后一次的操作。
图1 未设置防抖演示
图2 设置防抖演示
3.实现方式
防抖的核心就是利用定时器(Settimeout)来实现的, 核心思路如下:
- 先创建一个定时器变量
- 每次事件触发都需要先判断是否有定时器,如果有先清除以前的定时器
- 如果没有定时器,则开始定时器,存入到定时器变量里面
- 定时器里面写函数调用
let timeout = null
/**
* 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function debounce(func, wait = 500, immediate = false) {
// 清除定时器
if (timeout !== null) clearTimeout(timeout)
// 立即执行,此类情况一般用不到
if (immediate) {
const callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
if (callNow) typeof func === 'function' && func()
} else {
// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
timeout = setTimeout(() => {
typeof func === 'function' && func()
}, wait)
}
}
export default debounce
上述代码可写在utils文件夹下的debounce.js中,作为公共方法使用。当我们需要使用防抖功能的地方使用时,可参考一下连续点击按钮的示例:
<template>
<up-button @click="toDebounce">未设置防抖</up-button>
</template>
<script setup>
import debounce from '@/utils/debounce.js';
// 防抖
const toDebounce = () => {
debounce(submit)
}
const submit = () => {
console.log('点击防抖')
}
</script>
4.防抖的好处
- 减少冗余计算与渲染:
防抖机制确保高开销操作(如网络请求、复杂计算、DOM操作)仅在用户停止操作后的指定时间内执行一次,有效避免了因连续快速操作引发的重复计算和界面频繁刷新。这不仅提升了系统性能,还确保了界面的稳定性和流畅性。
- 提升效率与响应速度:
通过减少不必要的函数执行,防抖技术显著减轻了CPU和内存的负担,特别是在资源受限的设备(如移动设备或低性能设备)上,这种优化尤为重要。此外,减少请求或计算的数量意味着系统能够更快地响应真正需要处理的请求,从而提高了整体响应速度。
- 节约网络资源:
在需要发起网络请求的场景中(如搜索建议、表单验证等),防抖技术能够避免因用户快速输入而导致的多次无谓请求。这不仅节约了网络带宽,还减轻了服务器的负担,有助于提升系统的可扩展性和稳定性。
- 优化用户体验:
防抖技术通过确保仅在用户完成操作(如停止输入、停止滚动等)后才执行相应动作,避免了频繁操作和响应给用户带来的“卡顿”感或失控感。这种自然的界面反应不仅提升了用户体验的流畅性,还增强了用户对系统的信任和满意度。
三、节流(throttle)
1.节流的概念
节流(throttle)是一种常用的性能优化技术,用于限制某个函数在一定时间内的执行频率。具体来说,节流函数允许在一段时间内只执行一次回调函数,即使在这段时间内触发了多次事件。这有助于防止因为频繁触发事件而导致的性能问题。
用一个简单的示例来理解节流:乘坐高铁时,列车不会随到随发,而是按照固定的时间表发车。比如每小时有一班车,无论这期间有多少人想要乘车,都只能等待最近的一班车次。
2.应用场景
以下是节流常用的应用场景:
- 滚动事件监听:
当用户滚动页面时,滚动事件会频繁触发。如果不加以控制,可能会导致性能问题或不必要的资源消耗。通过节流技术,可以限制滚动事件处理函数的执行频率,例如每秒只执行一次,从而避免性能瓶颈。
- 窗口大小调整:
当用户调整浏览器窗口大小时,会触发resize
事件。这个事件同样可能频繁发生,导致页面布局频繁重绘或重新计算。通过节流,可以确保布局调整只在用户停止调整窗口大小一段时间后才执行,从而减少不必要的计算和渲染。
- 动画或过渡效果:
在实现动画或过渡效果时,如果过度频繁地更新动画状态或重新渲染界面,可能会导致性能问题。通过节流技术,可以控制动画的更新频率,确保动画的流畅性和性能。
3.实现方式
节流的核心思想是在规定的时间间隔内,无论触发多少次事件,函数都只会执行一次。所以实现的最关键的地方就是检查时间,如果当前的时间小于上一次执行的时间加上延迟的时间,那就不执行。根据这一思想,我们实现代码如下:
let timer;
let flag
/**
* 节流原理:在一定时间内,只能触发一次
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function throttle(func, wait = 500, immediate = true) {
if (immediate) {
if (!flag) {
flag = true
// 如果是立即执行,则在wait毫秒内开始时执行
typeof func === 'function' && func()
timer = setTimeout(() => {
flag = false
}, wait)
}
} else if (!flag) {
flag = true
// 如果是非立即执行,则在wait毫秒内的结束处执行
timer = setTimeout(() => {
flag = false
typeof func === 'function' && func()
}, wait)
}
}
export default throttle
4.节流的好处
- 提高性能:
通过减少高频率事件的触发次数,节流能够显著减轻浏览器或应用程序的负担,尤其是在处理如滚动、窗口调整大小、键盘事件等频繁触发的事件时。
- 防止过度渲染:
在前端开发中,特别是在React、Vue等框架中,节流可以有效防止组件因频繁状态变化而导致的过度渲染,从而提高应用的响应速度和用户体验。
- 资源管理
对于一些需要消耗大量资源(如CPU、内存或网络请求)的操作,节流能够确保这些操作在合理的时间间隔内执行,从而避免资源耗尽或性能瓶颈。
- 用户体验:
通过减少不必要的UI更新或操作,节流可以提升应用的流畅性和响应性,使用户体验更加顺畅。
- 控制动画频率:
在实现动画效果时,节流可以控制动画帧的渲染频率,确保动画既平滑又高效。
- 防止服务器过载:
对于需要频繁与服务器交互的应用,节流可以减少请求的频率,从而减轻服务器的负载,防止因请求过多而导致的服务器响应延迟或崩溃。
四、防抖和节流的区别
简单来说,防抖和节流的区别:
防抖:连续的事件触发中,只有在最后一次事件发生后的指定延迟时间结束后,函数才会被执行一次。
节流:无论事件触发多少次,函数只会在设定的时间间隔内执行一次。
下面用示例图直观的展示防抖和节流的区别:
图3 防抖点击事件示例
图4 节流点击事件示例
五、总结
在具体的使用场景中,防抖和节流应该如何选择可参考以下建议:
- 根据事件触发频率:如果事件触发频率较高,且希望在一定时间内只执行一次函数,可以选择节流。如果事件触发频率较低,但需要在用户稳定操作完成后才执行函数,可以选择防抖。
- 根据用户需求:如果用户需求是在连续操作后得到最终结果(如搜索框输入),则防抖更适合。如果用户需求是在持续操作中不断得到反馈(如页面滚动加载数据),则节流更适合。
需要注意的是,防抖和节流是可以一起使用的,防抖和节流在实际运用中的性能优化选择应取决于具体的应用场景和需求。