手撕防抖和节流

1,172 阅读4分钟

在开发过程中,总会遇到函数需要防抖和节流的情况,我将从以下几个方面来刨析防抖和节流

1.什么是防抖和节流

函数防抖

在频繁触发的模式下,我们只识别 “一次” 「识别第一次、也可以只识别最后一次」

函数节流

降低触发的频率,它能识别 “多次” 「浏览器有自己的最快反应时间,例如:谷歌57ms IE1017ms,这样在我们的疯狂操作下,谷歌浏览器的频率是5ms执行一次,节流是降低这个频率,比如我们设定频率是300ms,在疯狂触发的时候,我们控制间隔300ms才让其执行一次」

2.防抖和节流的使用场景

点击事件一般以 防抖为主「但是有些需求也是节流」 键盘输入事件 或者 滚动条滚动事件 都是以 节流为主

3.函数防抖debounce

假设fn()是我们触发的点击事件,debounce()函数执行后,需要将fn()执行结果返回

function debounce(func) {
    return function proxy() {
        func()
    }
}

传递的参数中也应该包括wait 频率时间immediate 是否立即执行

传递参数的过程中可能会出现以下几种情况

 debounce(fn)
 debounce(fn,300)
 debounce(fn,true)
 debounce(fn,300,true)

针对这几种情况,需要对传递的参数做以下判断处理

     /**
         * debounce  函数防抖
         *  @params
         *      func[functio]需要执行的函数
         *      wait[number]延时的时间,默认300
         *      immediate[boolean]是否立即执行,默认false
         *  @return
         *      func执行的结果
        */
    function debounce(func,wait,immediate) {
        if(typeof func !== 'function') throw new TypeError('func is not a');
        if(typeof wait === 'boolean'){
            immediate = wait;
            wait = 300;
        }
        if(typeof wait !== 'number') wait = 300;
        if(typeof immediate !== 'boolean') immediate = false;
        return function proxy() {
            func()
        }
    }

接下来,该处理核心代码,防抖就是在一定时间内不能触发多次只生效一次; 一定时间内,那我们就设置一个定时器,来控制是否执行下一次操作; 但是当我们马上点击第二次的时候,定时器就应该重新设置,再次等待300ms,所以需要做以下处理;

function debounce(func,wait,immediate) {
        if(typeof func !== 'function') throw new TypeError('func is not a');
        if(typeof wait === 'boolean'){
            immediate = wait;
            wait = 300;
        }
        if(typeof wait !== 'number') wait = 300;
        if(typeof immediate !== 'boolean') immediate = false;

        var timer = null;
        return function proxy() {
            if(timer)clearTimeout(timer);
            timer = setTimeout(function () {
                if(timer){
                    clearTimeout(timer);
                    timer = null;
                }
                func();
            },wait);
        }
    }

还差最后一步,需要设置立即执行;如果immediate为true,那就不需要先执行定时器,而是应该先执行函数func(),但是需要在判断一下,立即执行是否是在上一个定时器执行完毕。

function debounce(func,wait,immediate) {
        if(typeof func !== 'function') throw new TypeError('func is not a');
        if(typeof wait === 'boolean'){
            immediate = wait;
            wait = 300;
        }
        if(typeof wait !== 'number') wait = 300;
        if(typeof immediate !== 'boolean') immediate = false;

        var timer = null;
        return function proxy() {
            var isNow = !timer && immediate;//timer为null代表上一个定时器执行完毕,可以再次“立即执行”
            if(timer)clearTimeout(timer);
            timer = setTimeout(function () {
                if(timer){
                    clearTimeout(timer);
                    timer = null;
                };
                !immediate?func():null;
            },wait);
            isNow?func():null;
        }
    }

4.函数节流 throttle

节流是单位时间内触发频率降低,假设fn()是绑定的页面滚动的触发事件,函数throttle()最终返回的是fn()的执行结果。 且对应funcwait两个参数的处理

function throttle(func,wait) {
    if(typeof func !== 'function') throw new TypeError('func is not an function');
    if(typeof wait !== 'number') wait = 300;
    return function proxy() {
        func();
    }   
}

throttle()的核心思想,就是在wait时间内只执行一次,超过wait再执行第二次。 设置一个定时器,判断距离wait时间还剩多少时间。如果小于0,证明已经超过等待时间,可以执行;如果大于0,证明还未等待到时间,需要继续执行定时器

function throttle(func,wait) {
    if(typeof func !== 'function') throw new TypeError('func is not an function');
    if(typeof wait !== 'number') wait = 300;

    var timer = null,
        previous = 0; //之前的时间戳
    return function proxy() {
        var now = +new Date(), //当前的时间戳
            remaining = wait - (now - previous);//距离wait时间还有多少毫秒
        if(remaining <= 0){ //超过等待时间,立即执行
            func();
        }else if(!timer){
            timer = setTimeout(function () {//未超过等待时间,先等待剩余时间
                func();
            }, remaining);
        }
    }   
}

再执行完等待时间后,需要清除timer,并且将previous设置为当前的时间。

/**
* thorttle  函数节流
*  @params
*      func[functio]需要执行的函数
*      wait[number]延时的时间,默认300
*  @return
*      func执行的结果
*/
function throttle(func,wait) {
    if(typeof func !== 'function') throw new TypeError('func is not an function');
    if(typeof wait !== 'number') wait = 300;

    var timer = null,
        previous = 0; //之前的时间戳
    return function proxy() {
        var now = +new Date(), //当前的时间戳
            remaining = wait - (now - previous);//距离wait时间还有多少毫秒
        if(remaining <= 0){ //超过等待时间,立即执行
            if(timer){
                clearTimeout(timer);
                timer = null;
            }
            func();
            previous = +new Date();
        }else if(!timer){
            timer = setTimeout(function () {//未超过等待时间,先等待剩余时间
                if(timer){
                    clearTimeout(timer);
                    timer = null;
                }
                func();
                previous = +new Date();
            }, remaining);
        }
    }   
}

防抖debounce()和节流throttle()大致思路如上,参考于underscorelodash源码。