js util类—防抖和节流

110 阅读4分钟

本篇文章中的防抖和节流方法均已添加进Jsutl,该库为一个功能性的函数库,欢迎大家引用及Star或者提出宝贵的意见。

防抖及节流的个人理解

函数防抖函数节流在面试中都是老生常谈的问题,那么今天就让我来说说自己对这两个概念的理解吧。

函数防抖的概念和例子

函数防抖(debounce)

所谓的防抖,实际上就是在触发事件n秒后,才执行回调函数,如果在这n秒内又重新触发了事件,则重新计时

举个栗子:

// 模拟接口请求
const request = (val) => {
    setTimeout(() => {
        console.log('request:', val);
    }, 100)
}

let $normalInput = document.getElementById("normalInput")

$normalInput.addEventListener("input", (e) => {
    request(e.target.value)
})

normal-input.gif

上面展示的是我们正常情况下在input框内输入内容后请求接口的行为,可以看到每当我们输入一个内容就会请求一次接口。这样无疑照成了资源的浪费,加大了服务器的压力。

这个时候我们是不是可以考虑,在用户输入一个完整的内容后,再请求服务器资源呢?

答案是:当然可以!防抖(debounce)可以完美的解决这一痛点

上案例:

// 模拟接口请求
const request = (val) => {
    setTimeout(() => {
        console.log('request:', val);
    }, 100)
}

const debounceFn = function (fn, delay, immediate) {
    let timer;
    return (args) => {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
             fn(args);
        }, delay);
    };
};
let $debounceInput = document.getElementById("debounceInput")
let debounceInput = debounceFn(request, 1000)
$debounceInput.addEventListener("input", (e) => {
    debounceInput(e.target.value)
}) 

该函数为非立即执行版本,可以看到具体效果如下:

noImmediateDebounce.gif

可以看到当我们频繁输入的时候,并没有发送请求,直到停止输入后,等待我们设置的间隔时间后再执行函数。

既然有非立即执行版本,那么也就意味着有立即执行版本,代码如下:

let num = 0
// 模拟接口请求
const request = () => {
    num++
    console.log(num);
}
// 立即执行版本
const debounceFn = function (fn, delay) {
    let timer;
    return (args) => {
        // 每次执行防抖函数都清除定时器
        if (timer) clearTimeout(timer);
         // 判断防抖函数中的定时器是否在等待执行的时间间隔内
        const callNow = !timer
        timer = setTimeout(function () {
          // 等待间隔时间后将定时器id置为null
          timer = null
        }, delay);  
        // 如果没有设置定时器就立即执行
        if(callNow) {
           fn(args)
        }
    };
};
let $debounceButton = document.getElementById('debounceButton')
let debounceButtonFn = debounceFn(request, 1500)
$debounceButton.addEventListener('click', (e) =>{
    debounceButtonFn()
})

debounceButton.gif

此方法适用于按钮点击时,可以看到只有我们第一次点击按钮时才触发了打印事件,当我们在间隔时间内反复的触发事件时,是不会触发打印事件的。

试着将非立即执行和立即执行版本合并

该防抖函数存放在Jsutil,欢迎访问使用。

/**
 * @desc 函数防抖
 * @param fn 执行函数
 * @param delay 延迟执行的毫秒数
 * @param immediate 是否立即执行
 */
const _Debounce = (fn, delay, immediate) => {
    let timer;
    return (args) => {
        if (timer)
            clearTimeout(timer);
        // 立即执行
        if (immediate) {
            const callNow = !timer;
            timer = setTimeout(() => {
                timer = null;
            }, delay);
            if (callNow) {
                fn(args);
            }
        }
        else {
            timer = setTimeout(() => {
                fn(args);
            }, delay);
        }
    };
};

个人理解: 函数的防抖就好比游戏中的技能施法读条,等到读条结束后就释放技能,但在读条的过程中重复释放技能的话,又会重新进行读条。


函数节流的概念和例子

函数节流(throttle)

所谓的节流,保证在一定的间隔时间内只触发一次函数的执行。

函数节流一般分为两种类型:时间戳版本和定时器版本

这两种版本的区别在于,时间戳版本的函数触发是在设定时间开始的时候,定时器版本的函数触发时在设定时间结束的时候。

话不多收,直接上代码!

时间戳版本

time-throllte.gif 在持续触发函数的情况下,立即执行函数,并且每隔设定的间隔时间执行一次。

let num = 0;
const request = () => {
    num++;
    console.log(num);
};
const throttle = (fn, delay = 0) => {
    let time = 0;
    return (val) => {
        let now = Date.now();
        if (now - time >= delay) {
            fn(val);
            time = now;
        }
    };
};
const $input = document.querySelector('#input')
const $button = document.querySelector('#button')
$button.onclick = throttle(() => {
    request()
}, 2000)

定时器版本

timeout-throllte.gif 在持续触发函数的情况下,并不会立即执行,而是等待设定的时间间隔节后在执行,因为这个原因,当我们停止触发事件后,函数还是会执行一次。

let num = 0;
const request = () => {
    num++;
    console.log(num);
};
const throttle = (fn, delay = 0) => {
    let time = 0;
    return (val) => {
        if (!time) {
             time = setTimeout(() => {
                fn(val)
                time = null
            }, delay)
        }

    };
};
const $input = document.querySelector('#input')
const $button = document.querySelector('#button')
$button.onclick = throttle(() => {
    request()
}, 2000)

和防抖一样,我们也可以试着将时间戳版本和定时器版本结合

该节流函数存放在Jsutil,欢迎访问使用。

/**
 * @desc 函数节流
 * 连续触发函数,但是在n秒中只执行一次,可以稀释函数执行的频率
 * 好比技能cd 释放技能后要等待冷却时间
 * @param fn 执行函数
 * @param delay 延迟执行的毫秒数 默认为500ms
 * @param type 1 表示时间戳类型(立即触发)  2 表示定时器(到时间再触发)  3 时间戳+定时器版: 实现第一次触发可以立即响应,结束触发后也能有响应
 */
const _Throttle = (fn, delay = 500, type = 1) => {
    let previous = 0, timer;
    return (val) => {
        if (type === 1 || type === 3) {
            let now = Date.now();
            // 当前时间-先前时间 > 延迟时间
            if (now - previous > delay) {
                fn(val);
                previous = now;
            }
        }
        if (type === 2 || type === 3) {
            if (!timer) {
                timer = setTimeout(() => {
                    clearTimeout(timer);
                    timer = null;
                    fn(val);
                }, delay);
            }
        }
    };
};

个人理解: 函数的节流就好比游戏中技能CD,在单位时间内只能施法一次。

欢迎大家指正