#防抖与节流函数(立即执行版)
适用场景:
- 防抖(Debounce):输入框搜索、窗口 resize 等“高频触发,停下来再放行”场景
- 节流(Throttle):滚动监听、拖拽、按钮连续点击等“固定时间内最多执行一次”场景
下面给的是通用 TypeScript 版本,默认都是“立即执行(leading)”。
#1. 防抖(立即执行)
特点:
- 第一次触发立即执行
- 在
wait时间内重复触发不再执行 - 只有停止触发达到
wait后,才允许下一次立即执行
type AnyFn = (...args: any[]) => any
type Debounced<T extends AnyFn> = ((...args: Parameters<T>) => ReturnType<T> | undefined) & {
cancel: () => void
flush: () => ReturnType<T> | undefined
}
export function debounceImmediate<T extends AnyFn>(
fn: T,
wait = 300,
): Debounced<T> {
let timer: ReturnType<typeof setTimeout> | undefined
let lastThis: unknown
let lastArgs: Parameters<T> | undefined
let lastResult: ReturnType<T> | undefined
const debounced = function (this: unknown, ...args: Parameters<T>) {
lastThis = this
lastArgs = args
const canRunNow = !timer
if (timer) {
clearTimeout(timer)
}
// 只负责“解锁”,不做尾调用
timer = setTimeout(() => {
timer = undefined
}, wait)
if (canRunNow) {
lastResult = fn.apply(lastThis, lastArgs)
return lastResult
}
return lastResult
} as Debounced<T>
debounced.cancel = () => {
if (timer) {
clearTimeout(timer)
timer = undefined
}
}
debounced.flush = () => {
debounced.cancel()
if (lastArgs) {
lastResult = fn.apply(lastThis, lastArgs)
}
return lastResult
}
return debounced
}#2. 节流(立即执行)
特点:
- 第一次触发立即执行
- 在
wait时间内,后续触发最多在结尾补一次(可关) - 常用于滚动、拖拽这类持续触发场景
type AnyFn = (...args: any[]) => any
type Throttled<T extends AnyFn> = ((...args: Parameters<T>) => ReturnType<T> | undefined) & {
cancel: () => void
flush: () => ReturnType<T> | undefined
}
interface ThrottleOptions {
trailing?: boolean
}
export function throttleImmediate<T extends AnyFn>(
fn: T,
wait = 300,
options: ThrottleOptions = {},
): Throttled<T> {
const { trailing = true } = options
let timer: ReturnType<typeof setTimeout> | undefined
let lastThis: unknown
let lastArgs: Parameters<T> | undefined
let lastResult: ReturnType<T> | undefined
const run = () => {
if (!lastArgs) return
lastResult = fn.apply(lastThis, lastArgs)
lastArgs = undefined
}
const throttled = function (this: unknown, ...args: Parameters<T>) {
lastThis = this
lastArgs = args
// 立即执行
if (!timer) {
run()
timer = setTimeout(() => {
timer = undefined
// 可选尾调用
if (trailing && lastArgs) {
run()
}
}, wait)
return lastResult
}
// 冷却中:只更新最后一次参数,等待尾调用
return lastResult
} as Throttled<T>
throttled.cancel = () => {
if (timer) {
clearTimeout(timer)
timer = undefined
}
lastArgs = undefined
}
throttled.flush = () => {
if (timer) {
clearTimeout(timer)
timer = undefined
}
if (lastArgs) {
run()
}
return lastResult
}
return throttled
}#3. 使用示例
import { debounceImmediate, throttleImmediate } from '@/utils/function'
// 输入搜索:立即查一次,连续输入期间不再触发
const onSearch = debounceImmediate((keyword: string) => {
console.log('search =>', keyword)
}, 400)
// 滚动监听:立即执行 + 冷却结束补最后一次
const onScroll = throttleImmediate(() => {
console.log('scroll =>', Date.now())
}, 200)
window.addEventListener('scroll', onScroll)
// 页面销毁时记得移除监听并 cancel
// window.removeEventListener('scroll', onScroll)
// onScroll.cancel()#4. 常见坑
- 防抖和节流都要注意组件卸载时
cancel(),避免内存泄漏或无效回调 wait太小会让“优化”意义不大,太大又会影响交互手感- 搜索场景常用防抖,滚动/拖拽场景常用节流,不要反着用
#5. 精简版(立即执行)
如果你只想要最短可用代码,用下面这版即可。
// 立即执行防抖:第一次立刻执行,wait 内重复触发不执行
export function debounceNow<T extends (...args: any[]) => void>(fn: T, wait = 300) {
let timer: ReturnType<typeof setTimeout> | null = null
return function (this: unknown, ...args: Parameters<T>) {
if (!timer) {
fn.apply(this, args)
}
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
timer = null
}, wait)
}
}// 立即执行节流:第一次立刻执行,wait 内只执行一次
export function throttleNow<T extends (...args: any[]) => void>(fn: T, wait = 300) {
let canRun = true
return function (this: unknown, ...args: Parameters<T>) {
if (!canRun) return
canRun = false
fn.apply(this, args)
setTimeout(() => {
canRun = true
}, wait)
}
}最小使用示例:
const onInput = debounceNow((v: string) => {
console.log('search:', v)
}, 300)
const onScroll = throttleNow(() => {
console.log('scroll')
}, 200)
