一、概念
作用
【防抖】和【节流】的作⽤都是防⽌某个函数被多次调⽤.
区别
假设⽤户⼀直触发某个函数,且每次触发函数的间隔⼩于期望时间 【wait】,【防抖】的情况下只会调⽤【⼀次】,⽽【节流】的情况下会每隔⼀定时间【wait】调⽤函数。
PS:防抖动和节流本质是不⼀样的。防抖动是将多次执⾏变为最后⼀次执⾏,节流是将多次执⾏变成每隔⼀段时间执⾏.
二、防抖 - debounce
PS:防抖函数,返回函数连续调⽤时,空闲时间必须 >= wait,func 才会执⾏.
1. 防抖的简单实现
/**
* @param {function} func 传入的回调函数
* @param {number} wait 期望时间
* @return {function} 返回包装的函数
* */
function debounce(func, wait = 50) {
let timer = null;
// 利用闭包保存变量,否则函数一执行完之后,变量就被初始化
return function (...params) {
// 如果已经设定过定时器了就清空上⼀次的定时器
// 否则当前函数被多次触发时,就会存在多个定时器,那么当前函数也会被多次执行
if (timer) {
clearTimeout(timer);
}
// 开始⼀个新的定时器
timer = setTimeout(() => {
func.apply(null, params);
}, wait);
}
}
以上的实现方式拥有 缺陷:
- 如果⽤户调⽤该函数的间隔⼩于 wait 的情况下,上⼀次定时器的时间还未到就被清除了,并不会去执⾏函数
- 防抖函数只能在最后的时刻被调⽤.⼀般的防抖会有 immediate 选项,表示是否⽴即调⽤
2. 防抖实现(包含 immediate 功能)
/*
* @param {function} func 回调函数
* @param {number} wait 期望时间间隔
* @param {boolean} immediate 设置为 ture 时,会⽴即调⽤函数
* @return {function} 返回客户调⽤函数
*/
function debounce(func, wait = 50, immediate) {
let timer = null;
let context, args;
// 抽成 later 函数是为了方便多次执行,不需要重复声明
const later = () => setTimeout(() => {
// 延迟函数执⾏完毕,清空缓存的定时器序号
timer = null;
// 如果已经需要立即执行,那么就不需要在延迟执行一次
if (!immediate) {
func.apply(context, args);
}
// 手动进行垃圾回收
context = args = null;
}, wait);
return function (...params) {
// 1.未创建延迟函数,先创建
if (!timer) {
timer = later();
// 1.1需要立即执行一次,就直接执行
if (immediate) {
func.apply(this, params);
} else {
// 1.2不需要立即执行,保存当前 this 和 入参 args,保证在 later 函数执行时回调函数 func 的正确执行
context = this;
args = params;
}
} else {
// 2. 延迟函数已执行
// 2.1 先清除原来的并重新设定⼀个
clearTimeout(timer);
// 2.2 保证重新计时
timer = later();
}
}
}
三、节流 - throttle
/**
* 节流函数,返回函数连续调⽤时,func 执⾏频率限定为【次 / wait】
*
* @param {function} func 回调函数
* @param {number} wait 表示时间的间隔
* @return {function} 返回客户调⽤函数
*/
function throttle(func, wait = 50, options) {
let timer = null;
let result;
let previous = 0;
return function (...args) {
// 每次执行函数,先保存当前时间
let now = +new Date();
// 计算剩余时间
let remaining = wait - (now - previous);
// remaining <= 0 代表首次执行
// remaining > wait 代表定时器比预期执行时更晚,但也符合执行时机
if (remaining <= 0 || remaining > wait) {
// 如果存在定时器就清理掉否则会调⽤⼆次回调
if (timer) {
clearTimeout(timer);
timeout = null;
}
// 执行函数时,把当前时间,记录为上一次执行时间
previous = now;
result = func.apply(this, args);
} else if (!timer) {
// 判断是否设置了定时器
// 没有的话就开启⼀个定时器
timer = setTimeout(() => {
// 执行函数时,把当前时间,记录为上一次执行时间
// 由于这里是异步的,所以不能直接使用 now,必须要重新获取时间
previous = +new Date();
result = func.apply(this, args);
}, remaining);
}
// 返回结果值
return result;
}
}