防抖和节流在js中有很多应用的场景,比如监听滚动条事件,输入字符搜索次数的优化等等。不能只知其然而不知其所以然,所以有必要了解下实现的原理。
防抖是在一段时间内不能执行某种操作。比如在搜索框这个场景,只有在500ms后没有输入新的字符,才会去调用接口进行搜索。
节流则是有规律的执行某种操作。比如监听滚动条事件这个场景,在一直滚动时,每隔300ms会执行一次滚动条事件的回调。
/**
* 函数防抖
* @param func 目标函数
* @param delay 延迟时间
* @param immediate 是否立即执行,是指在触发后是否先执行一遍目标函数
*/
function debounce (func, delay, immediate) {
let timer = null; // 通过闭包来保存timer的值
let _debounce = function () {
const context = this;
const args = arguments;
if (timer) {
clearTimeout(timer);
}
if (immediate) {
let callNow = !timer; // 如果timer为null说明是第一次执行
timer = setTimeout(() => {
timer = null;
}, delay);
if (callNow) {
func.apply(context, args);
}
} else {
timer = setTimeout(() => {
func.apply(context, args);
}, delay);
}
}
_debounce.cancel = function () {
clearTimeout(timer);
timer = null;
}
return _debounce;
}
/**
* 函数节流
* @param func 目标函数
* @param delay 延迟时间
* @param options options.leading:是否在刚触发后立即执行目标函数
* options.trailing:是否在停止触发后还执行一次目标函数
*/
function throttle (func, delay, options) {
let timer;
let previous = 0; // 上一次执行的时间点
options = options || {};
const _throttle = function () {
const context = this;
const args = arguments;
const now = +new Date();
// 如果leading为false,则设置上一次执行的时间点为当前,自然也就不会立马执行目标函数了
if (!previous && options.leading === false) {
previous = now;
}
// 计算距离上次执行的时间点还剩多少时间
const remaining = delay - (now - previous);
// 如果剩余时间小于等于0,说明从上次执行到当前已经超过了设定的delay间隔
// 如果剩余时间大于设定的delay,说明当前系统时间被修改过
if (remaining <= 0 || remaining > delay) {
if (timer) {
clearTimeout(timer);
timer = null;
}
func.apply(context, args);
previous = now;
} else if (!timer && options.trailing === true) {
timer = setTimeout(() => {
// 如果leading为false代表下次触发后不需要立马执行目标函数,所以设置为0,在17行才会顺利进入判断
previous = options.leading === false ? 0 : +new Date();
func.apply(context, args);
timer = null;
}, remaining); // 此处设定为计算距离上次执行的时间点剩余的时间,使用setTimeout保证了即使后续不触发也能再执行一次目标函数
}
}
_throttle.cancel = function () {
clearTimeout(timer);
timer = null;
previous = 0;
}
}
原文:https://www.cnblogs.com/cloverclover/p/13667643.html