防抖和节流

141 阅读3分钟

在前端开发的过程中,我们经常会需要绑定一些持续触发的事件,如 resize、scroll、mousemove 等等,但有些时候我们并不希望在事件持续触发的过程中那么频繁地去执行函数。

通常这种情况下我们怎么去解决的呢?一般来讲,防抖和节流是比较好的解决方案。

防抖函数

使用场景:点击按钮不做任何处理的时候,当你点击速度快的时候,方法就会触发多次,若是方法内部有请求的话,就会造成服务器资源的浪费,更容易造成数据的混乱。所以这时我们要对按钮进行防抖操作。

<button id="btn">按钮</button>
let button = document.getElementById('btn');

function btnClick () {
    console.log('点击了')
}

// 防抖处理函数
/**
    handleFun 最终需要执行的事件
    wait 事件触发之后多久开始执行
    immediate 控制执行第一次还是最后一次,false为执行最后一次(即最后一次点击事件完成的时候)
*/
function myDebounce (handleFun, wait, immediate) {
    // 参数类型以及默认值处理
    // 这里如果handleFun传的不是一个方法,我们就抛出错误
    if(typeof handleFun !== 'function') throw new Error('handleFun must be an function');
    // 这里如果没传时间,我们默认给500毫秒
    if(typeof wait === 'undefined') wait = 500;
    // 这里如果wait传的布尔值,那么我们默认把布尔值传给immediate,并且给wait一个默认时间
    if(typeof wait === 'boolean') {
        immediate = wait;
        wait = 500;
    }
    // 这里如果immediate传的不是布尔值,那么我们默认给immediate false
    if(typeof immediate !== 'boolean') immediate = false;
    let timer = null;
    return function proxy (...args) {
        let that = this;
        let init = immediate && !timer;
        clearTimeout(timer);
        // 点击最后一次执行
        timer = setTimeout(() => {
            timer = null;
            // immediate为false时 延时执行事件
            !immediate ? handleFun.call(that,...args) : null;
        },wait)
        // 点击第一次就执行
        init ? handleFun.call(that,...args) : null;
    }
}

button.onclick = myDebounce(btnClick, 500, true)

节流函数

使用场景:滚轮的滚动事件scroll,窗口的resize事件等,都是会频繁的触发,相比较快速点击,scroll事件和resize事件更频繁,而且这类事中我们不是要只执行一次,而是我们需要在一定时间内只执行一次,所以这时我们要对事件进行节流处理。

function scrollFun () {
    console.log('滚动了')
}

// 节流处理函数
/**
    handleFun 最终需要执行的事件
    wait 事件触发之后多久开始执行
    immediate 控制执行第一次还是最后一次,false为执行最后一次(即最后一次点击事件完成的时候)
*/
function myThrottle (handleFun, wait) {
    // 参数类型以及默认值处理
    // 这里如果handleFun传的不是一盒方法,我们就抛出错误
    if(typeof handleFun !== 'function') throw new Error('handleFun must be an function');
    // 这里如果没传时间,我们默认给500毫秒
    if(typeof wait === 'undefined') wait = 500;
    // 定义变量记录上次执行的时间
    let pervious = 0;
    let timer = null;
    return function proxy (...args) {
        let that = this;
        // 定义变量记录此次执行的时间
        let now = new Date();
        // 时间间隔
        let interval = wait - (now - pervious);
        // 此时说明是一个非高频操作,可以执行handleFun
        if(interval <= 0) {
            clearTimeout(timer);
            timer = null; 
            handleFun.call(that,...args)
            pervious = new Date();
        } else if (!timer){
            // 当我们发现当前系统中有一个定时器了,就意味着我们不需要在开启一个定时器了,不然就                是把所有在操作都延时操作了,就达不到节流的效果了
            // 此时就说明这次的操作发生在了我们定义时间范围内,那就不应该执行 handleFun
            // 这个时候我们就可以自定义一个定时器,让handleFun在interval之后去执行
            timer = setTimeout(() => {
                clearTimeout(timer);// 这个操作只是把系统中的定时器清除,但是timer中还有值
                timer = null; // 把timer也初始掉
                handleFun.call(that,...args)
                pervious = new Date();
            },interval)
        }
    }
}

window.onscroll = myThrottle(scrollFun, 500)