手写防抖节流及反思

99 阅读2分钟

防抖就是对高频率,连续触发的代码进行优化处理。使其在多次执行过程中,只执行一次。主要利用js闭包来实现。

防抖的实现有三步:

  • 防抖方法封装
  • 传参决定是否立即执行
  • this指向
基础防抖方法如下
function debounce(fn, timer) {
        let time = null;  //闭包保存定时器
        return function (e) {
            if (time) { 
                clearTimeout(time)
            } 
                time = setTimeout(() => {
                    fn()
                }, timer)  
        }
    }

debounce方法有两个参数

  1. fn:需要防抖的方法
  2. timer:连续触发的时间间隔

首先定义闭包来保存time参数,然后返回一个方法,返回方法里面的逻辑也比较简单,如果time有值那就清空这个time,然后设置time为定时器,它将在tmer间隔后执行方法fn。因为time是闭包,所以time会被缓存下来,带在返回方法上,因此在下一次触发后,先判断上次事件中time是否为空,为空则清除后再设置time,以此循环。

传参决定是否立即执行

原生防抖函数可以带个bollean值来判断是否立即执行,继续优化代码

function debounce(fn, isdo, timer) {
        let time = null;  //闭包保存定时器
        return function () {
            let that = this
            if (time) {
                clearTimeout(time)
            }
            if (isdo) {
                if (!time) {
                    fn()
                }
                time = setTimeout(() => {
                    time = null
                }, timer)
            } else {
                time = setTimeout(() => {
                   fn()
                }, timer)
            }

        }
    }

相比基础版,新加了isdo参数,思路其实是一样的,isdo为false,则和基础版一样,isdo为true,则立即执行,首先判断!time是否不为空,也就是time是否为null,time为null,则直接执行fn,然后将time设置为定时器,在timer后设置time为null,如果规定间隔连续触发,则time不为null,将会重新设置time,使得定时器重新开始,直到两次触发超过时间间隔。

- this指向

在fn方法中打印this,指向window,因此在调用时需要通过apply来让this绑定到调用debounce的对象上,绑定后打印为sub的dom对象。 下面是完整代码。

const sub = document.getElementsByTagName('input')[0];
    let con = document.getElementById('ind')
    let sum = 0
    function add() {
        console.log(this,'dadaw'); //<input type="submit">
        con.innerText = sum++
    }
    sub.addEventListener('click', debounce(add, true, 1000))
    function debounce(fn, isdo, timer) {
        let time = null;  //闭包保存定时器
        return function (e) {
            console.log(this);  // <input type="submit">
            let that = this
            if (time) {
                clearTimeout(time)
            }
            if (isdo) {
                if (!time) {
                    fn.apply(that,arguments)
                }
                time = setTimeout(() => {
                    time = null
                }, timer)
            } else {
                time = setTimeout(() => {
                    fn.apply(that,arguments)
                    time = null
                }, timer)
            }

        }
    }

补一个手写节流的方法,思路一样的,就是在同一时间段只执行一次,执行后更新oldTime

    var cont = 1
    let but = document.getElementsByTagName('input')[0]
    let conent = document.getElementById('ind')
    let someThing = function (e) {
        conent.innerText = cont++
    }
    but.addEventListener('click', expenditure(someThing, 2000))
    function expenditure(fn, timer) {
        let oldTime = 0;
        return function () {
            nowTime = new Date().getTime()
            if (nowTime - oldTime > timer) {
                fn()
                oldTime = nowTime
            }
        }
    }