防抖节流
防抖和节流是一种优化高频执行js的手段。比如我们需要监听鼠标的scroll事件,然后做某些事情,但是scroll事件触发非常频繁,如果每次都执行回调,势必会有性能损耗。又比如有一个输入框,我们需要监听用户的输入内容,内容变化去请求接口获取数据,但是如果用户以极快的速度输入内容,那么中间请求接口返回的数据就将被覆盖,完全没有必要,这也极大浪费了资源,降低了性能。诸如此类的情况非常多,所以为了优化体验,提高性能,需要对类事件的调用次数做限制,这就是防抖和节流。
两者的区别是防抖是在一定时间内连续触发的事件只执行最后一次,而函数节流则在一段时间内只执行一次。
举个现实中的🌰:
防抖就像我们坐公交车,司机会等所有乘客都上车之后,等一会儿确定没有人之后就会开车。而节流就像坐地铁,停留规定的时间,到点就开车。
防抖
应用场景
- 输入框搜索
- 按钮的重复点击
- 上拉滚动加载
- 用户的缩放事件
- ......
实现原理
- 当事件触发时并不会立即执行回调,而是会等待一段时间
- 如果在等待期间再次触发事件,会重新继续等待
- 只有等待期间无新的事件触发才会执行回调
代码实现
function debounce(func, wait = 0, immediate = false, callback = () => {}) {
let timer;
//是否第一次调用过了
let immediateInvoked = false;
const debounced = function (...args) {
return new Promise((resolve, reject) => {
if (timer) {
clearTimeout(timer);
timer = null;
}
if (immediate && !immediateInvoked) {
try {
const result = func.apply(this, args);
callback(null, result);
resolve(result);
} catch (e) {
callback(e);
reject(e);
}
immediateInvoked = true;
}
timer = setTimeout(() => {
try {
let result = func.apply(this, args);
callback(null, result);
resolve(result);
} catch (e) {
callback(e);
reject(e);
}
immediateInvoked = false;
}, wait);
});
};
// 外部可以通过调用cancel方法取消
debounced.cancel = function () {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
return debounced;
}
节流
应用场景
- 下拉刷新
- 鼠标移动
- 拖拽组件
- ......
实现原理
代码实现
/**
*
* @param {*} func
* @param {*} wait
* @param {*} options
* leading 是否要执行第一次,第一次触发的时候必须执行回调
* trailing 是否要执行最后一次
* @returns
*/
function throttle(func, wait = 0, options = { leading: true, trailing: true, callback: () => {} }) {
const { leading, trailing } = options;
//上次回调的执行时间
let lastExecTime = 0;
let timer;
const throttled = function (...args) {
return new Promise((resolve, reject) => {
//当前的时间戳
const currentTime = Date.now();
//如果说lastExecTime为0说明是第1次,并且第1次不要执行
if (lastExecTime === 0 && !leading) {
lastExecTime = currentTime;
}
//上次执行的时间加上等待的时间就是下一次要执行的时候
const nextExecTime = lastExecTime + wait;
if (currentTime >= nextExecTime) {
if (timer) {
clearTimeout(timer);
timer = null;
}
try {
let result = func.apply(this, args);
callback(null, result);
resolve(result);
} catch (e) {
callback(e);
reject(e);
}
lastExecTime = currentTime;
} else {
if (trailing) {
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
try {
let result = func.apply(this, args);
callback(null, result);
resolve(result);
} catch (e) {
callback(e);
reject(e);
}
lastExecTime = Date.now();
}, nextExecTime - currentTime);
}
}
});
};
// 外部可以通过调用cancel方法取消
throttled.cancel = function () {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
return throttled;
}