防抖和节流
debounce - 防抖 throttle - 节流
防抖和节流很相似,但是不同,本质上是控制函数在一段时间内执行多少次的技术。
相同点是避免函数在单位时间连续执行,从而提高性能。 两者的本质区别:throttle 可以保证函数在单位时间 T 内至少执行一次,而 debounce 不会保证。
防抖
延迟单位时间后执行回调,如果函数在单位时间里再次触发,则重新计时。
使用场景:
- 防止按钮多次点击:向后台提交表单时,只执行最后一次点击的逻辑。
- 后端搜索场景:在输入框停止输入后,发起 http 请求。
基础防抖
js
function debounce(fn, delay) {
let timer; // 暂存 timeout 定时器函数
const debounced = (...args) => {
timer && clearTimeout(timer) // 每次调用 debounced 函数时,取消暂存的定时器,避免多次调用 setTimeout 回调
// 重置定时器,在停止调用 debounced 函数至少 delay 毫秒后调用 fn
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
return debounced
}立刻执行防抖
js
function debounceLeading(fn, delay) {
let timer;
const debounced = (...args) => {
// 判断是否可调用 fn
// 1. 首次点击调用
// 2. 距离上次调用 delay 毫秒后可再次调用
if (!timer) {
fn.apply(this, args)
}
timer && clearTimeout(timer) // 连续调用 debounced 函数时取消定时器
// 重置定时器
timer = setTimeout(() => {
timer = undefined // 在上次调用 delay 毫秒后重置 timer
}, delay)
}
return debounced
}立刻执行且执行最后一次调用防抖
js
function debounceLeadingTrailing(fn, delay) {
let timer;
const debounced = (...args) => {
// 判断是否可调用 fn
// 1. 首次点击调用
// 2. 距离上次调用 delay 毫秒后可再次调用
if (!timer) {
fn.apply(this, args)
}
timer && clearTimeout(timer) // 连续调用 debounced 函数时取消定时器
// 重置定时器
timer = setTimeout(() => {
timer = undefined // 在上次调用 debounced 函数 delay 毫秒后重置 timer
fn.apply(this, args) // 上次调用 debounced 函数 delay 毫秒后调用 fn
}, delay)
}
return debounced
}节流
在单位时间内,只执行一次回调。如果单位时间连续触发多次函数,有一次会生效。
使用场景:
- 拖拽场景:固定时间只执行一次,防止超高频连续的位置变化引起的卡顿。
- 缩放场景:监听浏览器 resize。
- 动画场景:避免短时间内多次触发动画。
基础节流
js
function throttle(fn, delay) {
let delayOk = true // delay 间隔是否重置
const debounced = (...args) => {
if (delayOk) { // 在 delay 期间禁止执行
fn.apply(this, args)
delayOk = false // 调用 fn 后,开始计算 delay 间隔
setTimeout(() => {
delayOk = true // delay 完成,重置间隔
}, delay)
}
}
return debounced
}执行最后一次调用节流
js
function throttleTrailing(fn, delay) {
let timer
let last = 0 // fn 上次调用时间
const debounced = (...args) => {
// 调用 fn,重置上次调用 fn 时间
function execFn() {
fn.apply(this, args)
last = Date.now()
}
timer && clearTimeout(timer) // debounced 调用前清空定时器
const diff = Date.now() - last // 本次调用 debounced 距上次调用 fn 的间隔
if (diff > delay) { // 如果调用间隔超过 delay
execFn() // 调用 fn, 重置上次调用 fn 时间
} else {
// 如果间隔小于等于 delay,重置定时器和定时器延迟时间
// 因为要保证本次调用是在距上次 delay 毫秒后调用 , 则本次延时时间为 delay - diff
timer = setTimeout(() => {
execFn()
}, delay - diff)
}
}
return debounced
}