防抖(debounce)
实现防抖的核心逻辑:只触发最后一次调用的函数。 将频繁触发的事件合并为一次去执行
应用场景:按钮提交、搜索
const debounce = (func, wait=50)=> {
let timer = 0
return function(...args){
if(timer) clearTimeout(timer)
timer = setTimeout(()=>{
func.apply(this,args)
},wait)
}
}
每次调用防抖函数时,都会清除之前的定时器(clearTimeout(timer))。
然后重新设置一个新的定时器(setTimeout),在指定的 wait 时间后调用 func。
如果在 wait 时间内再次调用防抖函数,会重置定时器,从而延迟 func 的执行
timer
timer 是一个变量,用于存储 setTimeout 返回的标识符
在防抖函数中,timer
的作用是用来跟踪是否已经存在一个尚未完成的定时器。如果存在,就清除它(取消之前的延时执行),从而保证只保留最新的一次函数调用的延时执行。
timer 的生命周期
timer = setTimeout(() => {
console.log("Hello");
}, 1000);
setTimeout 返回的标识符存储到 timer 变量中。
在这 1000ms 的时间内,timer 表示这个定时器(比如:数字 1 或 Timeout 对象)。
如果 clearTimeout(timer) 被调用,就会取消这个定时器。
如果定时器触发(即到达 1000ms 后),timer 并不会自动变为 null,它仍然是之前的值
为什么需要手动管理 timer?
如果不手动管理 timer,就无法取消之前的定时器,从而失去防抖效果
const debounce = (func, wait = 1000) => {
let timer = null; // 初始化为 null
return function (...params) {
if (timer) {
// 如果已有定时器,清除它
clearTimeout(timer);
}
// 创建新的定时器
timer = setTimeout(() => {
func.apply(this, params);
}, wait);
};
};
// 示例
const log = (message) => console.log(message);
const debouncedLog = debounce(log, 1000);
debouncedLog("Hello"); // 设置定时器
debouncedLog("World"); // 清除上一个定时器,设置新的
debouncedLog("Final"); // 再次清除旧的,设置新的
timer
的变化过程
时间点 | 操作 | timer 的值 | 定时器状态 |
---|---|---|---|
0ms | 调用 debouncedLog("Hello") | 标识符 1 (定时器 ID) | 定时器开始计时 |
500ms | 调用 debouncedLog("World") | 标识符 2 (新定时器 ID) | 旧定时器被清除 |
800ms | 调用 debouncedLog("Final") | 标识符 3 (新定时器 ID) | 再次清除旧定时器 |
1800ms | 定时器触发,执行 func ("Final" ) | 3 | 定时器已完成 |
调用方式的说明
debounce
函数返回一个新的函数, 本质上是一个高阶函数
debouncedLog("Hello", "Alice")
是正确的用法,直接调用返回的防抖函数并传入参数
...params
收集了参数 ["Hello", "Alice"]
。
节流
目的:使得一定时间内只触发一次函数
核心逻辑:是通过 timer
判断是否允许执行函数,防止短时间内频繁触发
像dom的拖拽,如果用消抖的话,就会出现卡顿的感觉,因为只在停止的时候执行了一次,这个时候就应该用节流,在一定时间内多次执行,会流畅很多
function throttle(func, delay) {
let timer = 0; // 定义一个定时器标识符
return function (...args) { // 使用扩展运算符接收参数
if (timer) return; // 如果已有定时器在运行,直接返回
timer = setTimeout(() => {
func(...args); // 直接调用传递的函数
timer = 0; // 清空定时器标识符
}, delay);
};
}
const throttledLog = throttle((message) => console.log(message), 2000);
throttledLog('Message 1'); // 第一次调用,输出 'Message 1'
throttledLog('Message 2'); // 第二次调用,直接返回
setTimeout(() => throttledLog('Message 3'), 2500); // 超过 2 秒后,输出 'Message 3'
执行过程分析
- 第一次调用
timer
初始值为 0
。
调用 throttledLog('Message 1')
:timer
为 0
,进入 setTimeout
。定时器开始计时(delay = 2000
毫秒),输出 'Message 1'
。
- 第二次调用
在定时器未完成的情况下(2000ms 内),timer
不为 0
。
调用 throttledLog('Message 2')
:if (timer)
条件成立,直接 return
。此时,'Message 2'
未被打印。
- 定时器触发
超过 2000ms 后,定时器触发:执行 func(...args)
。将 timer
设置为 0
,表示可以再次执行。
- 第三次调用
超过 2000ms 后(timer
为 0
),调用 throttledLog('Message 3')
:再次设置新的定时器,输出 'Message 3'
。
关于this
- 如果
func
使用了this
,需要显式保存上下文(const context = this
)并通过apply
传递。 - 如果
func
是一个不依赖上下文的函数(如箭头函数),则可以直接使用func(...args)
。
应用场景:
- 拖拽元素时,触发高频的鼠标移动事件,可能导致性能问题
- 在表单中监听用户输入并执行实时验证或搜索时,可能触发过多的函数调用
- 用户滚动页面加载新内容时,需避免频繁的网络请求
- 防止用户在短时间内多次点击按钮,触发多次操作(如提交表单、发送请求) -- 限制按钮的点击频率。
- 浏览器窗口调整时触发大量的
resize
事件,导致页面性能下降