详解防抖与节流的概念
- Published on
- — 714 Words
- Authors
- Name
- Richard Shih
- @realshiddong
防抖(Debounce)与节流(Throttle)可以说是前端领域中非常高频的概念,它们都用于在高频事件中防治逻辑函数被频繁调用,是一种性能优化的方案。
它们的基本区别是:
- 防抖:事件持续触发,当事件停止触发 n 毫秒之后才执行一次逻辑函数
- 节流:事件持续触发,在事件触发的时间段内,每隔 n 毫秒执行一次逻辑函数
防抖
防抖功能一般是在一段时间之后执行具体的逻辑函数,所以一般使用定时器 setTimeout 实现。
基础封装
/**
* Creates a debounced function that delays invoking `func` until after `wait`
* milliseconds have elapsed since the last time the debounced function was
* invoked, or until the next browser frame is drawn.
*
* @param { Function } fn The function to debounce.
* @param { number } delay The maximum time `fn` is allowed to be delayed before it's invoked.
* @return { Function } Returns the new debounced function.
*/
function debounce(fn, delay) {
let timer = null
return (...args) => {
if (timer) {
clearTimeout(timer) // 如再次调用,则清理上次的定时器,重新开始计时
}
timer = setTimeout(() => {
fn(...args) // 传递参数
}, delay)
}
}
高级封装
在 JS 的常用工具函数库 Lodash 中,debounce 支持
flush()
立即调用cancel()
取消调用
其他的功能诸如使用 immediate
参数支持立即调用、返回函数调用结果等实现都比较简单,如下仅为示例, 具体的处理逻辑可以参见 lodash
// 添加 flush 与 cancel 的逻辑
function debounce(fn, delay) {
let timer = null
let lastInvokeArgs = []
// 用于触发执行逻辑函数
const invoke = () => {
return fn(...lastInvokeArgs)
}
// 防抖函数主体
const debounced = (...args) => {
clearTimeout(timer) // 如再次调用,则清理上次的定时器,重新开始计时
lastInvokeArgs = args
timer = setTimeout(invoke, delay)
}
// 立即调用
const flush = () => {
invoke()
clearTimeout(timer)
}
// 取消调用
const cancel = () => {
clearTimeout(timer)
}
debounced.flush = flush
debounced.cancle = cancel
return debounced
}
节流
节流的概念是:事件持续触发,在触发的事件段内,每隔 n 毫秒执行一次逻辑函数。将这个 n 毫秒视为一个单位时间。 若单位时间内触发了多次,只有一次生效,即只会执行一次逻辑函数。
节流可以分为两类:
- 首调用:在触发的事件段内,在一个单位时间结束后,才执行一次逻辑函数 —— 可通过时间戳方式实现
- 尾调用:在触发的事件段内,在一个单位时间开始后,立即执行一次逻辑函数 —— 可通过设置定时器的方式
首调用
/**
* Creates a throttled function that only invokes `fn` at most once per
* every `wait` milliseconds (or once per browser frame).
* @param { Function } fn The function to throttle.
* @param { number } wait The number of milliseconds to throttle invocations to.
*/
function throttle(fn, wait) {
let lastInvokeTime = 0
return (...args) => {
const now = +Date.now()
const remaining = wait - (now - lastInvokeTime)
const canInvoke = remaining <= 0
if (canInvoke) {
lastInvokeTime = now
fn(...agrs)
}
}
}
尾调用
function throttle(fn, wait) {
let timer = 0
return (...args) => {
if (timer) {
return
}
timer = setTimeout(() => {
clearTimeout(timer)
fn(...args)
}, [wait])
}
}