防抖节流

351 阅读5分钟

防抖(debounce)和节流(throttle)


防抖(debounce):是指函数触发后一段时间再执行,如果期间重复触发函数,则重新计时,最后只执行一次

案例1:搜索框的提示,如果在输入的时候就触发请求函数获取提示内容,浏览器则会在输入时不断的发送请求,但使用防抖,则可以当输入出现停顿且停顿到一定的时间再执行函数(出现停顿则表示用户的确需要提示,停顿到一定的时间再发送请求获取提示,如果用户快速输入,则说明不需要提示,浏览器也就没必要发送请求去获取提示)

案例2:浏览器的onresize事件,如果在调整浏览器大小时会高频率执行函数,若函数中是dom操作,高频率的dom操作会影响页面显示和浏览器的性能,使用防抖,则在浏览器停止调整后的一段时间再执行函数,减少不必要的dom操作(红宝书上的节流例子就是这个,我觉得可能是节流和防抖并没有分得那么清楚,防抖也属于节流吧)

实现1:红宝书的节流例子

采用定时器,触发就删除上一个定时器,重新创建一个定时器,重新计时

/*fun是待执行函数,delay是指间隔时间,
context指执行上下文,
因为setTimeout里的执行函数
的上下文对象默认为window对象*/
function debounce(fn,delay,context){
    clearTimeout(fn.id)
    fn.id=setTimeout(fn.bind(context),delay)
}


window.onresize = function(){
    debounce(fun,500)
}

//执行函数
function fun(){
}
实现2:采用闭包,自定义实现lodash,实现取消和可立即执行功能。
//使用防抖
e.addEventListener('input',debounce(fun,1000,true))

//防抖函数实现
function debounce = function(fn,delay){
    var timer=null//定时器
    //待执行函数
    var debounced =  function(){
        var _that = this//保存调用函数的上下文
        var _arguments = arguments//保存传入参数
        if(timer){
            clearTimeout(timer)
        }
        timer=setTimeout(function(){
                fn.apply(_that,_arguments)
            },delay)
        
    }
    //取消执行
    debounced.cancel = function(){
        if(timer){
            clearTimeout(timer)
            timer=null
            
        }
            
    }
    //返回待执行函数
    return debounced
}

优化是否立即执行,即重复触发函数时,第一次会执行

例如当在搜索框快速输入时,会立即执行一次获取提示,如果之后有停顿且停顿到一定时间才会再执行函数获取提示,后续再触发事件时,仍会立即执行一次,然后同样当出现停顿时再执行一次

function debounce(fn,delay,leading){
    var timer=null//定时器
    var leading=leading||false//第一次是否立即执行
    var debounced =  function(){
        var _that = this//保存调用函数的上下文
        var _arguments = arguments//保存传入参数
        if(timer){
            clearTimeout(timer)
        }
        if(leading){
            var isInvoke=false//记录是否已立即执行
            //无定时器,说明是事件触发的开始,立即执行
            if(!timer){
                fn.apply(_that,_arguments)
                isInvoke=true
            }
            /*更新定时器,若回调函数最终执行了,则timer为null,
            表示已经间隔了足够的时间,下次事件再次触发时会立即执行
            (执行上面if语句内的代码)*/
            timer=setTimeout(function(){
                timer=null
                /*如果事件只触发了一次,因为已经立即执行了,
                所以不用再执行定时器里的函数*/
                if(!isInvoke){
                    fn.apply(_that,_arguments)
                }
            },delay)
            
        }
        else{
            timer=setTimeout(function(){
                fn.apply(_that,_arguments)
            },delay)
        }
    }
    //取消执行
    debounced.cancel = function(){
        if(timer){
            clearTimeout(timer)
            timer=null
        }
            
    }
    
    return debounced
}


节流(throttle):在一段时间内重复调用函数,但函数只会执行一次,区别防抖,防抖是触发函数后一般不会立即执行,而是一段时间内不再触发函数时再执行,而节流是调用函数时会立即执行,但如果和上次执行的函数时间间隔不满足要求则不会执行。

案例1:小游戏中点击按钮飞机发射子弹时,重复点击按钮,一定时间内飞机也只发射一颗子弹

实现1:采用定时器和一个开关
function throttle(fn,interval){
    var timer=null,flag=true//flag作为一个开关,为true则执行函数
    function throttled(){
        var _this=this,
        _arguments=arguments
        if(flag){
            //flag为true,执行函数,并在一定时间后再恢复为true
            flag=false
            fn.apply(_this,_arguments)
            setTimeout(()=>{
                flag=true
            },interval)   
        }
        
    }
    return throttled
}
优化是否执行最后一次

利用一个定时器来控制,当flag为false时,则创建一个定时器,在规定的时间间隔后执行函数,如此哪怕未到时间间隔也会执行最后一次函数,如果在定时器内的函数执行前,flag为true且触发了函数,则清除定时器,只执行当前事件的函数

function throttle(fn,interval,option){
    var timer=null,flag=true,option=option||false//默认不执行最后一次触发的函数
    function throttled(){
        var _this=this,
        _arguments=arguments
        if(flag){
            if(timer){
                //清除定时器
                clearTimeout(timer)
                //用于下次在等待时间内再触发时,创建定时器
                timer=null
            }
            flag=false
            fn.apply(_this,_arguments)
            setTimeout(()=>{
                flag=true
            },interval)
        }
        //设置执行最后一次
        else if(!timer&&option){
            timer=setTimeout(()=>{
                flag=false
                fn.apply(_fn,_arugments)
                timer=null
                setTimeout(()=>{
                    flag=true
                },interval)
            },interval)
        }
    }
    throttled.cancel=function(){
        if(timer){
            clearTimeout(timer)
            timer=null
        }
    }
    return throttled
}
实现2:采用时间戳
var throttle = function(fn,interval){
    var pre = 0//上次执行的时间
    var throttled = function(){
        var _this = this
        var _arguments = arguments
        var now = new Date().getTime()
        if(now-pre>=interval){
            fn.apply(_this,_arguments)
            pre=now
        }
    }
    return throttled
}


优化是否执行最后一次执行

采用定时器实现,如果需要最后一次执行,则在第一次触发事件时创建定时器,如果最后一次触发事件时与上次执行函数的时间不满足要求,则不会执行函数,最终执行定时器中的回调函数

var throttle = function(fn,interval,option){
    var pre = 0,timer=null,//定时器
    option=option||false//默认不执行最后一次
    var throttled = function(){
        var _this = this
        var _arguments = arguments
        var now = new Date().getTime()
        if(now-pre>=interval){
            //清除定时器
            if(timer){
                clearTimeout(timer)
                timer=null
            }
            fn.apply(_this,_arguments)
            pre=now
        }
        /*第一次触发事件或者在定时器被清除后再触发事件,创建定时器,
        确保当最后一次触发事件与最后一次执行函数的时间间隔<interval时,
        仍会再执行一次函数*/
        else if(!timer&&option){
            timer=setTimeout(function(){
                timer=null
                fn.apply(_this,_arguments)
                pre=now
            },interval)
        }
    }
    
    throttled.cancel=function(){
        if(timer){
            clearTimeout(timer)
            timer=null
            
        }
    }
    
    return throttled
}

以上的代码我都简单的测试过了,第一次在社区写文章,如果有疑问或者有错误欢迎指出呀~我代码主要是来自参考的文章,该文章写得比较清楚,而且还有优化返回值的,不过我还没来得及消化,下次再补充。

参考:codewhy公众号的文章《javascript防抖和节流》