防抖与节流
防抖
短时间内多次触发同一函数,只执行第一次/最后一次。比如说:一只小猫冷的发抖,稍用力按住它,它就不抖了。可以在它还没抖的时候按住,也可以在它抖了很久之后按住
普通防抖
/**
* 普通实现
* 函数防抖[函数高频率被调用只执行第一次/最后一次]
* @param {Function} func 需要防抖的函数
* @param {Number} [duration=1000] 防抖时长[多长时间内的点击需要防抖]
*/
function debounce (func, duration = 1000) {
let timer = null;
return function (...args) {
/*if (timer!== null)*/ clearTimeout(timer);
timer = setTimeout(() => {
clearTimeout(timer);
func.apply(this, args);
}, delay);
}
}
如果要给防抖函数绑定this
,使用方式如下
绑定this
function mClick() {
console.log('我被点名啦!');
}
debounce(mClick).bind(this);
有配置项的防抖
/**
* 函数防抖
* @param {Function} func 需要防抖的函数
* @param {Boolean} [first=true] 是否首次防抖
* @param {Number} [duration=1000] 防抖持续时长
* @returns
*/
function debounce(func, first = true, duration = 1000){
// 声明一个空的定时器id,为防抖做准备
let timer = null;
// 返回函数是因为这个防抖函数可能会被绑定/赋值给其他函数使用
return function (...args) {
first && !timer && func.apply(this, args);
// 不用判断timer是否存在,传入一个错误的 ID 给 clearTimeout()不会有任何影响;也不会抛出异常。
clearTimeout(timer);
timer = setTimeout(() => {
clearTimeout(timer);
// 非首次防抖则调用这个函数
!first && func.apply(this, args);
// 为下一次防抖做准备
timer = null;
}, duration);
};
}
可取消的防抖
/**
* 函数防抖
* @param {Function} func 需要防抖的函数
* @param {Boolean} [first=true] 是否首次防抖
* @param {Number} [duration=1000] 防抖持续时长
* @returns
*/
function debounce(func, first = true, duration = 1000){
// 声明一个空的定时器id,为防抖做准备
let timer = null;
let debounced = function (...args) {
first && !timer && func.apply(this, args);
// 不用判断timer是否存在,传入一个错误的 ID 给 clearTimeout()不会有任何影响;也不会抛出异常。
clearTimeout(timer);
timer = setTimeout(() => {
clearTimeout(timer);
// 非首次防抖则调用这个函数
!first && func.apply(this, args);
// 为下一次防抖做准备
timer = null;
}, duration);
};
debounced.cancel = function () {
clearTimeout(timer);
// 为下一次防抖做准备
timer = null;
}
// 返回函数是因为这个防抖函数可能会被绑定/赋值给其他函数使用
return debounced;
}
节流
一段时间内只允许函数执行一次
使用场景:滚动条滚动,过程中速度可能很快,但是肉眼捕捉不到,所以那些肉眼捕捉不到的函数调用是不必要的,故而节流,节省性能。
时间戳节流
首次执行,duration
期间内尾次不执行
会在第一次触发事件时立即执行[给事件绑定函数与真正触发事件的间隔一般大于duration
],以后每过duration
时长后,函数可以被再次触发执行。
/**
* 函数节流
* @param {Function} func 需要节流的函数
* @param {Number} [duration=1000] 节流持续的时长
*/
function throttle(func, duration = 1000) {
let prev = 0;
return function (...args) {
const now = Date.now();
// 两次函数被调用的时长大于节流时长
if (now - prev > duration) {
func.apply(this, args);
prev = Date.now();
}
}
}
绑定this
function mScroll () {
console.log('我滑滑梯啦!');
}
throttle(mScroll).bind(this);
定时器节流
第一次触发不会执行,而是在duration
时长后执行,当最后一次停止触发后,还会再执行一次函数。
/**
* 函数节流
* @param {Function} func 需要被节流的函数
* @param {Number} [duration=1000] 节流持续时长
*/
function throttle(func, duration = 1000) {
let timer = null;
return function (...args) {
// 定时器未执行结束,函数再次被调用不生效
if (!timer) {
timer = setTimeout(() => {
clearTimeout(timer);
func.apply(this, args);
timer = null;
}, duration);
}
}
}
时间戳 + 定时器的节流
首、尾次均执行。[两者交替使用,第一次是时间戳,第二次是定时器,第三次是时间戳,第四次是定时器……,理想状态下如此,但碍于定时器的消息队列处理等影响,有时可能不是这样。]
/**
* 函数节流
* @param {Function} func 需要被节流的函数
* @param {Number} [duration=1000] 节流持续时长
*/
function throttle (func, duration = 1000) {
let timer, prev = 0;
return function (...args) {
const now = Date.now();
// 解决第一次不执行
if (now - prev > duration) {
// 某个定时器执行结束,清除定时器
if (timer){
clearTimeout(timer);
// 如果定时器超过了duration时长仍然没有执行函数[被其他事情影响],则取消这个定时器,立即执行函数
timer = null;
}
func.apply(this, args);
prev = Date.now();
} else if (!timer) { // 解决最后一次不执行
// 两次调用时长小于duration时使用
timer = setTimeout(() => {
prev = Date.now();
func.apply(this, args);
timer = null;
}, duration);
}
};
}
有配置项的节流
/**
* 函数节流
* @param {Function} func 需要被节流的函数
* @param {Object} option 节流配置项
* option = {
* leading: false, // 禁止第一次执行
* trailing: false // 禁止最后一次执行
* }
* leading和trailing不能同时为false[同为false,节流失效]
* @param {Number} [duration=1000] 节流持续时长
*/
function throttle (func, option, duration = 1000) {
!option && (option = { leading: true, trailing: true });
let timer, prev = 0;
return function (...args) {
const now = Date.now();
if (!(option.leading || prev)) {
prev = now;
}
// 解决第一次不执行
if (now - prev > duration) {
// 某个定时器执行结束,清除定时器
if (timer){
clearTimeout(timer);
// 如果定时器超过了duration时长仍然没有执行函数[被其他事情影响],则取消这个定时器,立即执行函数
timer = null;
}
func.apply(this, args);
prev = Date.now();
} else if (!timer && option.trailing) { // 解决最后一次不执行
// 两次调用时长小于duration时使用
timer = setTimeout(() => {
clearTimeout(timer);
prev = Date.now();
func.apply(this, args);
timer = null;
}, duration);
}
};
}
可取消的节流
/**
* 函数节流
* @param {Function} func 需要被节流的函数
* @param {Object} option 节流配置项
* option = {
* leading: false, // 禁止第一次执行
* trailing: false // 禁止最后一次执行
* }
* leading和trailing不能同时为false[同为false,节流失效]
* @param {Number} [duration=1000] 节流持续时长
*/
function throttle (func, option, duration = 1000) {
!option && (option = { leading: true, trailing: true });
let timer, prev = 0;
let trottled = function (...args) {
const now = Date.now();
if (!(option.leading || prev)) {
prev = now;
}
// 解决第一次不执行
if (now - prev > duration) {
// 某个定时器执行结束,清除定时器
if (timer){
clearTimeout(timer);
// 如果定时器超过了duration时长仍然没有执行函数[被其他事情影响],则取消这个定时器,立即执行函数
timer = null;
}
func.apply(this, args);
prev = Date.now();
} else if (!timer && option.trailing) { // 解决最后一次不执行
// 两次调用时长小于duration时使用
timer = setTimeout(() => {
clearTimeout(timer);
prev = Date.now();
func.apply(this, args);
timer = null;
}, duration);
}
};
trottled.cancel = function () {
clearTimeout(timer);
timer = null;
prev = 0;
}
return trottled;
}
window.requestAnimationFrame节流
节流时机不可控[函数不知道什么时候就被调用了,因为不知道什么时间浏览器会重绘]
/**
* 函数节流
* @param {Function} func 需要节流的函数
*/
function throttle(func) {
let lock = false;
return function (...args) {
!lock && window.requestAnimationFrame(() => {
func.apply(this, args);
lock = false;
});
lock = true;
}
}
参考
[JavaScript] 节流(throttle)-防抖(debounce) 不懵圈指北
使用 requestAnimationFrame 替代 throttle 优化页面性能