一、Debounce(防(手)抖)
1.1 功能
- 立即执行型:手抖导致
指定时长threshold内触发多次同一FunctionA时,会立即执行FunctionA,但是在接下来的threshold内再次触发时,不会再次执行并且重置需要等待的时长为threshold,直至下次触发FunctionA的时间间隔与当前时间间隔大于threshold - 延迟执行型:手抖导致
threshold内触发多次同一FunctionA时,只会让最后一次触发的FunctionA执行
1.2 代码构思
- 因为需要延迟执行FunctionA,因此执行方式通过
setTimeout回调来实现 - 通过
闭包特性将timeout对象放在闭包环境中供内部函数使用 - 当多次触发FunctionA时,通过不断重新设置timeout实现只触发最后一次的FunctionA
- 基于防抖功能,需要入参有:
func(Function:触发的FunctionA)threshold(Number:执行FunctionA的等待阈值)immediate(Boolean:是否首次触发后FunctionA就立即执行)
1.3 代码
function debounce(func, threshold = 500, immediate = false) {
// 通过闭包方式给func挂载一个timeout
let timeout = null
// 防抖处理过的函数
return function() {
const _this = this
const _args = arguments
// 触发时发现已有timeout则重置timeout
if(timeout) {
clearTimeout(timeout)
}
if(immediate) {
// 若需立即执行则执行FunctionA后设置timeout
// 保证接来下的threshold个时间内
// FunctionA也可以通过timeout重置
if(!timeout) {
func.apply(_this, args)
}
clearTimeout(timeout)
timeout = setTimeout(() => {
clearTimeout(timeout)
timeout = null
}, threshold)
} else {
// 非立即执行则通过不断刷新timeout以保证最后一次执行
clearTimeout(timeout)
timeout = setTimeout(() => {
func.apply(_this, _args)
}, threshold)
}
}
}
// 挂载到原型链
Function.prototype.debounce = function (threshold = 500, immediate) {
// this指向函数本身
const _func = this;
return debounce(_func, threshold, immediate);
};
二、 Throttle(节流)
2.1 功能
- 立即执行型:在
指定时长threshold内触发FunctionB一万次,FunctionB只会立即执行once,直到本轮threshold结束 - 延迟执行型:在
指定时长threshold内触发FunctionB一万次,FunctionB只会在threshold的最后一刻执行once
2.2 代码构思
- 基于时间戳实现
- 通过比较当前时间戳和上次触发时间戳的差值
△t和指定时长threshold来判断是否当前轮次已经结束 - 和debounce一样通过闭包给FunctionB挂载一个lastTs来记录上次触发的事件戳
- 首次触发时,默认上次触发时间戳为执行throttle时的时间戳
- 使用时间戳无法合理做到延迟执行,因此适用于实现为立即执行型throttle
- 综上得出入参有二:
func(Function:触发的FunctionA)threshold(Number:每轮的总时长)
- 通过比较当前时间戳和上次触发时间戳的差值
- 基于timeout实现
- 通过查看当前触发节点是否有timeout来判断当前轮次是否已经结束
- 和debounce一样通过闭包给FunctionB挂载一个timeout用于轮次记录Ï
- 执行时机可用参数控制,比时间戳会灵活很多
- 综上得出入参有三:
func(Function:触发的FunctionA)threshold(Number:每轮的总时长)immediate(Boolean:是否在每轮的起点执行)
2.3 代码
// 基于时间戳实现
function throttleByTs(func, threshold = 500) {
let lastTs = Date.now()
return function() {
// 仅当△t大于等待时长才会触发
if(Date.now() - lastTs > threshold ) {
const _args = arguments
func.apply(this, _args)
// 重置时间戳
lastTs = Date.now()
}
}
}
// 基于timeout实现
function throttleByTimeout(func, threshold = 500, immediate = false) {
let timeout = null
return function() {
if(!timeout) {
if(immediate) {
// 立即触发则执行函数并创建timeout
func.apply(this, _args)
timeout = setTimeout(() => {
clearTimeout(timeout)
timeout = null
}, threshold)
} else {
// 非立即触发则创建timeout,在timeout callback中执行函数
const _args = arguments
timeout = setTimeout(() => {
func.apply(this, _args)
// 清除本轮timeout
clearTimeout(timeout)
timeout = null
}, threshold)
}
}
}
}
// 挂载到原型链
Function.prototype.throttleByTs = function (threshold = 500) {
// this指向函数本身
const _func = this;
return throttleByTs(_func, threshold);
};
Function.prototype.throttleByTimeout = function (threshold = 500, immediate) {
// this指向函数本身
const _func = this;
return throttleByTimeout(_func, threshold, immediate);
};
三、使用场景
PRE:Debounce和Throttle下述缩写为D&T
- 减少不必要的重复网络请求:如在提交表单时重复点击提交并触发网络请求时
- 部分功能后端需要确保幂等:前端可以通过D&T以确保同一节点请求唯一
- DOM绑定scroll、resize等会短时间内高频触发的事件时,可对绑定的事件callback进行D&T以解决对callback的高频调用
注:Debounce和Throttle最大的区别在于Debounce需要不断重置等待周期,也就是当用户不断触发Function时,throttle的表现是周期性执行函数,而debounce的表现是从头到尾只会执行一次函数,因此具体使用Debounce还是Throttle需要根据实际场景考虑