JS:手搓一份防抖函数

127 阅读3分钟

前言:面临秋招,防抖节流是重要的考点,是前端用来优化高频事件的方法,写个文章梳理一下。

  • 防抖:规定时间内只执行一次。 (重新计算时间)

  • 逻辑:当事件被触发时,不会立即执行对应的处理函数,而是等待一段预设的时间。

  • 场景:掘金登录的时候,用户🦊一直狂摁登录按键,会导致多次登录请求,消耗性能。 因此采用防抖的方式,每次摁登录按键,清除之前的等待时间,再等一段时间,这样让狐狸先生最后一次的点击登录按键成功。

  • 项目中使用,引入lodash库:

    import _ from "lodash"
    
    <button onclick={_.debounce(()=>navigate("./home"),500)}/>
    //debounce()传入两个参数一个是函数体一个是number

注意到debounce中需要传入两个参数,一个是函数体fn,一个是时间delay,表示需要等待delay时间后执行fn,定下大体逻辑。

const debounce = (fn,delay)=>{
    //直接采用定时器 , delay时间后调用fn
        setTimeout(() => {
        fn()
    }, delay)
}

显然是有问题的,因为onclick需要接收一个函数,再调用它,改一下返回一个函数体,注意这里是函数体,如果是匿名函数则this在 V8 非严格模式 会指向window,node环境指向undefined

const debounce = (fn, delay) => {
    return function () {
        setTimeout(() => {
            fn()
        }, delay)
    }
}

还有问题,这里的定时器只会执行一次,明显不满足重新计算时间的要求,稍微改一下,创建一个timer,返回一个函数体,因为返回函数体中用上了timer,会形成一个闭包

const debounce = (fn, delay) => {
    let timer = null;
    return function () {
        //这里有个闭包 timer 
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn()
        }, delay)
    }
}

如果这个放到button里面会是什么样呢?

//<button onclick={debounce(()=>navigate("./home"),500)}/>
<button onclick={
    function () {
        //这里有个闭包 timer 每一次点击时的timer都是同一个哦
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            navigate("./home")
        }, 500)
    }
}/>

现在每次点击button,都会把上次的定时器清除,看起来大功告成。(你看看你后面,你再看看你后面,你再看看你后面......)

注意平常我们调用onclick中的会传入一个e(事件参数)以及其它参数,这里没法传事件参数,再改一点点代码。

js中的 "..." 有两种语义,一种是收集,一种是解构,所以我用"..."来收集传入的参数再用"..."将传入参数解构出来

const debounce = (fn, delay) => {
    let timer = null;
    return function (...args) {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn(...args)
        }, delay)
    }
}

这样就完成了吗,这里的fn函数是独立调用的,如果fn中有用this,this在 V8 非严格模式下会指向window,node环境指向undefined,这明显有bug,稍微再改一点,给 this 掰弯来。

const debounce = (fn, delay) => {
    let timer = null;
    return function (...args) {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, args)//apply接受的是数组
            //或者
            fn.call(this,...args)//call能接受独立的值
        }, delay)
    }
}

检查一遍,写回button里看看

// <button onclick={debounce(
//   (e)=>{ 
//        console.log(e)
//        }
//,500)}/>

 <button onclick={
     function (e) {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout((e) => {
         //   fn.apply(this, [e])//apply接受的是数组
          console.log(e)
        }, 500)
    }}/>

好像没问题欸嘿,面试一定写得出来。

结果

const debounce = (fn, delay) => {
    let timer = null;
    return function (...args) {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, delay)
    }
}