一、防抖 (Debounce)
1、什么是防抖?
防抖指的是在一段时间内,多次触发相同事件时,只执行最后一次触发的事件。也就是说,在一系列触发事件中,如果在指定的时间间隔内,发生了新的触发事件,那么前面的触发事件将被忽略,只有最后一次触发事件会被执行。
2、防抖的适用场景
防抖会延迟响应,适用于可等待的场景,如:
-
窗口调整 resize: 当窗口大小发生变化时,会频繁的触发 resize 事件,可以使用防抖来延迟执行某些操作,例如在调整结束后再重新计算布局或重新渲染页面。
-
防止误触: 例如支付按钮,需确保用户停止点击后才执行一次操作,避免重复执行引发错误。
-
搜索输入框: 当用户在输入框输入内容时,会连续触发 keyup 事件,可以使用防抖来延迟触发搜索:只有在用户停止输入后才会发送请求,避免频繁请求,降低服务器压力。
-
表单验证: 如手机号、邮箱格式校验,可以在用户停止输入后再进行校验,降低校验事件触发频率。
3、如何实现防抖?
// 防抖函数,func为需要防抖的函数,delay为延迟执行的时间
const debounce = (func, delay) => {
// 创建一个timeout变量,用于存储setTimeout的返回值
let timeout;
// 返回一个函数,该函数接受参数args,func在每次调用时都会执行防抖逻辑
return function (...args) {
// 保存当前函数的上下文
const context = this;
// 清除之前的定时器,确保只有在delay后才执行一次func
clearTimeout(timeout);
// 设置一个新的定时器,在delay后执行func,并传入之前保存的args和context上下文
timeout = setTimeout(() => {
func.apply(context, args);
}, delay)
}
}
如果防抖需要立即执行,可以添加immediate参数来控制
// 立即执行防抖函数,func为需要防抖的函数,delay为延迟执行的时间,immediate为是否立即执行
const debounceImmediate = (func, delay, immediate) => {
// 创建一个timeout变量,用于存储setTimeout的返回值
let timeout;
// 返回一个函数,该函数接受参数args,func在每次调用时都会执行防抖逻辑
return function (...args) {
// 保存当前函数的上下文
const context = this;
// 确保timeout不为null
if (timeout) clearTimeout(timeout);
// 如果immediate为true,则立即执行func,并设置timeout为null
if (immediate) {
const callNow = !timeout; // 第一次会立即执行,之后不会
timeout = setTimeout(() => {
timeout = null;
}, delay);
// 如果callNow为true,则立即执行func
if (callNow) func.apply(context, args);
} else {
// 如果immediate为false,则延迟执行func,并设置timeout为null
timeout = setTimeout(() => {
func.apply(context, args);
}, delay);
}
}
}
二、节流 (Throttle)
1、什么是节流?
节流指的是在一段时间内,多次触发相同事件时,只执行一次事件。也就是说,无论触发事件发生多少次,在指定的时间间隔内只会执行第一次事件。
2、节流的适用场景
节流会即时响应,适用于需要周期性反馈的场景,如:
-
页面滚动: 当用户滚动页面时触发 scroll 事件,可以使用节流控制执行scroll处理函数的时间间隔,减少执行次数。
-
按钮点击: 例如游戏中的技能按钮,需立即响应第一次点击,但限制后续点击频率。
-
鼠标移动: 当用户移动鼠标时,可以使用节流限制鼠标移动事件 mousemove 的触发频率,避免过多的计算和页面渲染
-
动画帧率: 例如每秒60帧渲染,通过节流确保操作按固定间隔触发,保持流畅性
-
通信限制: 在用户发送消息时,可以使用节流限制在规定时间间隔内发送消息的次数,降低服务器压力
3、如何实现节流?
(1) 定时器写法
- 延迟执行
- 尾触发:停止触发后会再执行一次
// 延迟执行 节流函数
const throttle1 = (func, delay) => {
let timeout = null
return function (...args) {
const context = this
if (!timeout) {
timeout = setTimeout(() => {
func.apply(context, args)
timeout = null
}, delay)
}
}
}
原因:
- 在事件频繁触发时(如滚动中),
timeout是非空的,func不会被执行; - 直到某次触发后,
setTimeout被设置,delay毫秒后func执行; - 如果这时停止触发,那么这最后一次被 setTimeout 包裹的调用仍然会执行;
- 所以它会“补上”最后一次执行。
(2) 时间戳写法
- 立即执行
- 首触发:停止触发后不会再执行
// 立即执行 节流函数
const throttle2 = (func, delay) => {
let oldTime = Date.now();
return function (...args) {
let newTime = Date.now();
if (newTime - oldTime >= delay) {
func.apply(this, args);
oldTime = Date.now();
}
}
}
原因:
- 每次触发事件时都会判断当前时间和上次执行时间的间隔;
- 如果间隔未达到
delay,直接跳过,什么都不执行; - 一旦停止触发,函数不会再有机会执行,因为不会再触发时间间隔的判断;
三、防抖与节流的区别
举个🌰:
- 节流就像是首发站的公交车,每固定时间间隔发车
- 防抖就像电梯门,当有人进入,会等待某时间后关门,如果等待中途,又有人进入,则重新开始计时
| 特性 | 防抖 | 节流 |
|---|---|---|
| 执行时机 | 最后一次操作停止后执行 | 固定间隔内最多执行一次 |
| 执行次数 | 事件触发后可能只执行一次(若事件没有被再次触发) | 在事件持续触发期间,会根据固定间隔多次执行 |
| 适用场景 | 输入框搜索、窗口resize | scroll、mousemove |