函数防抖与函数节流

1,694 阅读4分钟

第一次听到这个东西是毕业后去头条面试的时候,面试说:说一下函数防抖是什么,wdt,啥玩意儿?只能默默的回答:不好意思,这个不是很熟!尴尬ing......

工作后一直在业务团队,真正用到这个东西的次数也不多,但是,我们得懂!一切的业务开发都要跟高标(mian)准(shi)靠齐,不然有啥子意思勒~

是啥玩意呢?

防抖和节流都是防止函数多次调用,在时间轴上控制函数的执行次数。

函数防抖(debounce)

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时,先计算时间后执行。

生活中的实例:有人进入电梯,电梯则在10秒后自动关门,如果10秒内又有人进入电梯,则电梯重新计时,自动关门。

function debounce(fn, delayTime) {
    let timer = null
    return function (args) {
        if (timer) {
            timer = null
            clearTimeout(timer)
        }
        let _this = this
        let _args = args
        timer = setTimeout(function () {
            fn.call(_this, _args)
        }, delayTime)
    }
}

let inputDom = document.getElementById('input2')

inputDom.addEventListener('keyup', debounce((e) => {console.log(e.target.value)}, 1000))

防抖原理

为了减少频繁发生的事件的资源请求,防抖是通过在用户没有操作的情况下,如果在等待时间内用户没有操作,则执行回调,如果有操作,继续等待相应时间间隔。所以在闭包函数内,初始化一个时间参数timer,如果timer为空,则表示回调事件没执行或者已执行完毕,代表在等待时间内用户没有操作,相反,如果timer不为null,清空timer,重新计时。

那如果我们想要先执行回调函数再计时,而不是时间到了再执行呢?也可以实现立即执行版:

function debounce(fn, delayTime, immediate) {
    let timer = null
    return function () {
        if (timer) {
            timer = null
            clearTimeout(timer)
        }
        let _this = this
        let _args = arguments
        if (immediate) {
            timer = setTimeout(() => {
                timer = null
            }, delayTime)            
            if (!timer) { fn.call(_this, _args) }        
        } else {
            timer = setTimeout(function () {
                fn.call(_this, _args)
            }, delayTime)
        }
    }
}

函数节流(throttle)

n秒内回调函数只会被执行一次,先执行后计算。

生活中的实例:玩飞机大战时,点击屏幕连发子弹,即使点击屏幕的频率再快,子弹也是平均间隔时间发射。

setTimeout实现函数节流

function throttle(fn, wait) {
    var timer = null
    if (timer) return 
    return function() {
        let _this = this 
        let args = arguments
        timer = setTimeout(function() {
            fn.apply(_this, args) timer = null
        },
        wait)
    }
}

时间差实现函数节流

function throttle(fn, wait) {
    var lastTime = 0
    return function() {
        let _this = this 
        let args = arguments 
        let nowTime = new Date() 
        if (nowTime - lastTime > wait) {
            fn.apply(_this, args) lastTime = nowTime
        }
    }
}

节流原理

函数节流的目的是为了让函数在特定时间内疚执行一次,这也是setTimeout的设计初衷,所以可以直接用setTimeout实现,当然执行完回调一定要将timer置空。另外一种思路就是计算触发时间和执行时间的差值,大于再执行回调。

使用场景

总结一下:函数防抖就是特定时间内触发动作的话,重新计时。函数节流是特定时间内触发动作只会有一次回调。

所以函数防抖适用的场景:监听窗口的滚动,缩放。高频发生的一些事件;

函数节流适用的场景:涉及与后端交互的按钮,由于网络原因或者其他原因,导致接口没有返回值,用户一直点点点的问题。

VUE 中使用函数防抖

业务需求中,有一个功能是点击按钮,执行sdk方法,吊起系统的拍照功能,在吊起的时候,会存在时间差,为了防止用户频繁点击,需要加上防抖的功能。

<template>
    <div @click="uploadPic">点击吊起系统拍照</div>
</template>

methods: {
    uploadPic: vueDebounce('wakeUpPhotos', 2000, true),
    wakeUpPhotos () {
        // todo 执行sdk方法,及回调逻辑
    }
}

vueDebounce (fnName, time, immediate) {
    let timer = null
    return function () {
        let args = arguments
        if (timer) { clearTimeout(timer) }
        // 判断是否需要立即执行
        if (immediate) {
            timer = setTimeout(() => {
                timer = null
            }, time)
            if (!timer) { this[fnName](...args) }
        } else {
            timer = setTimeout(() => {
                this[fnName](...args)
            }, time)
        }
    }
}
关键点:
  • **fnName**,是vuemethods的名称,不可以直接传回调函数,是因为this指向会不对
  • 执行回调的时候this指的是当前创建的vue对象,所以可以取到fnName对应的回调方法
  • 由于业务需求,所以一般都会加上立即执行的功能~