防抖和节流
防抖和节流都是前端开发中常用的性能优化方法,可以有效地减少一些高频率的事件触发而导致的性能问题。 都是通过函数包装的方式,对高频率事件进行限制。其中,防抖和节流的本质区别是时间间隔的判断时机不同。
- 防抖:在事件触发后的一段时间内,如果再次触发该事件,则重新计算时间间隔。直到该时间间隔内没有再次触发事件,才执行一次该事件。简单来说,防抖是在短时间内多次触发同一个函数,只执行最后一次,中间的都被抛弃了。
- 节流:在事件触发的一段时间内,只执行一次该事件,如果该时间段内事件再次触发,则不予理睬。简单来说,节流是按照一定时间间隔执行函数。
debounce 防抖
- 通过闭包,清除定时器实现再次触发重置事件,这样最简易的防抖就是实现了
function debounce(func, delay){
let timer = null;
const _debounce = function(fn){
if(timer) clearTimeout(timer);
timer = setTimeout(()=>{
fn();
},delay)
}
return _debounce;
}
- 加入第一次触发立即执行,并改变执行函数this指向问题
function debounce(func, delay,immediate){
let timer = null;
let isInvoke = false
const _debounce = function(...args){
if(timer) clearTimeout(timer);
// 判断是否是第一次进入触发而不是每次点击都触发
if(immediate && !isInvoke){
func.apply(this,args);
isInvoke = true;
}else{
timer = setTimeout(()=>{
func.apply(this, args);
// 执行结束回到初始值,这样下次再次触发还是第一次
isInvoke = false;
},delay)
}
}
return _debounce;
}
- 基本实现防抖,最后再加亿点点细节,加上取消方法
- 加上返回值,可以用回调或者Promise
function debounce(func, delay, immediate, resultCallback) {
let timer = null;
let isInvoke = false;
const _debounce = function(...args) {
return new Promise((resolve, reject) => {
if (timer) clearTimeout(timer);
if (immediate && !isInvoke) {
try {
const result = func.apply(this, args);
if (resultCallback) resultCallback(result);
resolve(result);
} catch (e) {
reject(e);
}
isInvoke = true;
} else {
timer = setTimeout(() => {
try {
const result = func.apply(this, args);
if (resultCallback) resultCallback(result);
resolve(result);
} catch (e) {
reject(e);
}
isInvoke = false;
timer = null;
}, delay);
}
});
};
_debounce.cancel = function() {
if (timer) clearTimeout(timer);
isInvoke = false;
timer = null;
};
return _debounce;
}
throttle 节流
- 固定时间固定频率执行方法,可以通过 设置的时间-现在时间-初始时间
- 当剩余时间小于等于零时执行,并把初始值设置成当前执行时间
function throttle(func, interval) {
let lastTime = 0;
const _throttle = function(...args) {
const nowTime = Date.now();
const remainTime = interval - (nowTime - lastTime);
if(remainTime<=0){
lastTime = nowTime;
func();
}
}
return _throttle;
}
上面是最基础的节流,会出现几个问题
当触发时间不够设置时间时
当触发时间超过设置时间不够第二次设置时间时
- 添加两个参数.让使用者来判断是否需要初始值和结尾值
- 加入取消方法
// leading:初始 trailing:结尾
function throttle(func, interval,options = { leading: true, trailing: true }) {
let timer = null;
let lastTime = 0;
const _throttle = function(...args) {
const nowTime = Date.now();
// 现在时间-初始时间 > 设置时间 为负 就会立即执行一次
if(!leading && !lastTime) lastTime = nowTime;
const remainTime = interval - (nowTime - lastTime);
if(remainTime<=0){
// 当timer存在时已经触发定时器,但是又再次执行,应清除定时器
if (timer) {
clearTimeout(timer);
timer = null;
}
lastTime = nowTime;
func.apply(this, args);
}
// !timer 是因为当timer为空时还没有触发结尾事件,当已经触发了不需要再次执行定时器
if(trailing && !timer){
timer = setTimeout(() => {
// 当定时器执行完成 判断leading 当为ture设置为0下次调用会执行最上面那个if
lastTime = !leading ? 0 : Date.now();
timer = null;
func.apply(this, args);
},remainTime)
}
}
_throttle.cancel = function() {
if (timer) clearTimeout(timer);
timer = null;
lastTime = 0;
};
return _throttle;
}
Ts 版本
/**
* @param func
* @param delay
* @param immediate
* @param resultCallback
*/
type Func = (...args: any[]) => any
export function debounce(func: Func, delay: number, immediate?: boolean, resultCallback?: Func) {
let timer: null | ReturnType<typeof setTimeout> = null;
let isInvoke = false;
const _debounce = function(this: unknown, ...args: any[]) {
return new Promise((resolve, reject) => {
if (timer) clearTimeout(timer);
if (immediate && !isInvoke) {
try {
const result = func.apply(this, args);
if (resultCallback) resultCallback(result);
resolve(result);
} catch (e) {
reject(e);
}
isInvoke = true;
} else {
timer = setTimeout(() => {
try {
const result = func.apply(this, args);
if (resultCallback) resultCallback(result);
resolve(result);
} catch (e) {
reject(e);
}
isInvoke = false;
timer = null;
}, delay);
}
});
};
_debounce.cancel = function() {
if (timer) clearTimeout(timer);
isInvoke = false;
timer = null;
};
return _debounce;
}
/**
* @param func
* @param interval
* @param options
* leading:初始 trailing:结尾
*/
export function throttle(func: Func, interval: number, options = { leading: false, trailing: true }) {
let timer: null | ReturnType<typeof setTimeout> = null;
let lastTime = 0;
const { leading, trailing } = options;
const _throttle = function(this: unknown, ...args: any[]) {
const nowTime = Date.now();
if (!lastTime && !leading) lastTime = nowTime;
const remainTime = interval - (nowTime - lastTime);
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastTime = nowTime;
func.apply(this, args);
}
if (trailing && !timer) {
timer = setTimeout(() => {
lastTime = !leading ? 0 : Date.now();
timer = null;
func.apply(this, args);
}, remainTime);
}
};
_throttle.cancel = function() {
if (timer) clearTimeout(timer);
timer = null;
lastTime = 0;
};
return _throttle;
}