怎么写好一个防抖工具函数?

104 阅读4分钟

防抖

防抖函数是前端工具函数必不可少的一个,它用于控制在事件被频繁触发时只执行一次函数。无论是处理用户输入、点击事件还是其他可能导致高频率触发的情况,函数防抖都能有效地减少不必要的执行次数,减少请求次数、优化性能,提升用户体验。

实现

实现函数防抖的核心思想是延迟执行函数,当事件触发时,设置一个定时器,在指定的延迟时间之后再执行函数。如果在这个延迟时间内又有新的事件触发,则重新计时,直到事件停止触发,延迟时间到达后执行函数,知道其原理后,你会手写一个防抖函数吗?

实现一个简单的防抖函数

    function myDebounce(handle,wait){
        //判断其参数正确性
        if(typeof handle !== 'function'){
            //这里的handle为托管事件,必须是一个函数,不为函数时应该抛出一个错误
            throw new Error('handle must be a Function')
        }
        if(typeof wait !== 'number'){
            //当传入的wait不是一个数字类型或者没有设置wait时应当给一个默认值
            wait = 300
        }
        //定义变量timer保存定时器
        let timer = null
        //返回一个句柄,当事件触发时,触发这个函数
        return function proxy(){
            // 如果 timer 不为 null 即在规定时间内已经触发过事件,则清除上一个定时器 这里就是防那个‘抖’ 
            //在规定时间内多次触发这里就会做拦截
             if (timer) {
                  clearTimeout(timer)
                  timer = null
              }
             timer = setTimeout(()=>{
                handle()
                //当过了所规定的时间后 清除其定时器,其目的是为了下一次正常操作时能正常触发事件,
                //并把timer 置 null
                clearTimeout(timer)
                timer = null
             },wait)
        }
    }

这只是一个简易的防抖函数,确实是防抖了,但是其中有很多缺陷:

  • 如果我需要传递参数怎么办?this指向?
  • 如果我的需求是在频繁的点击,我要求立即触发第一次,不用等待才触发?

解决传递参数和this指向问题

    //参数传递和this指向必定只能在防抖函数中返回的那个函数上下功夫 proxy
    function proxy(...args){
        //保存当前this
        let self = this
        if(timer) return
        timer = setTimeout(()=>{
            //通过call方法改变handle的this指向,保证其this为当前触发的对象,采用es6 ...(rest参数)
            //获取参数
            handle.call(self,args)
            clearTimeout(timer)
            timer = null
        })
    }

增加立即执行功能

    //如果需要立即去执行这个事件,就是所谓的在频繁的触发下,只触发第一次,并且是立即触发不等待
    //就需要额外的配置
    function myDebounce(handle,wait,immediate){
        //判断其参数正确性
        if(typeof handle !== 'function'){
            //这里的handle为托管事件,必须是一个函数,不为函数时应该抛出一个错误
            throw new Error('handle must be a Function')
        }
        
        //这里的参数处理可以采用es6的函数参数的默认值
        if(typeof wait !== 'number'){
            //当传入的wait不是一个数字类型或者没有设置wait时应当给一个默认值
            //如果这里在传参时第二个参数传递的是一个boolean,就把该值赋值给immediate
            immediate = wait
            wait = 300
        }
        
        //如果没有传递正确的immediate或者没传时应给immediate一个默认值
        if (typeof immediate !== "boolean") {
            immediate = false;
         }
         
        //定义变量timer保存定时器
        let timer = null
        
        //返回一个句柄,当事件触发时,触发这个函数
        return function proxy(...args){
            //保存当前this
            let self = this
                        
            //定义一个变量保存其immediate和timer的状态
            //这里这么做的目的是什么呢?
            //init就是实现立即触发的关键,当且仅当开启立即执行并且无定时器时立即触发事件
            //什么时候无定时器?那必定是第一次执行的时候
            let init = immediate && !timer
            
            // 清除上一个定时器 
            if(timer){
                clearTimeout(timer);
                timer = null;
            }
            timer = setTimeout(()=>{
                //如果开启了immediate,那必定不会执行定时器里面的handle,触发第一次后,在规定时
                //己按内点击都不会触发handle 实现防抖功能
               !immediate && handle.call(self,args)
               
               //执行到这里必须要清除其定时器,并把timer置null 以便下一次正常使用
               clearTimeout(timer);
               timer = null
            },wait)
            
            //这里是立即执行
            init && handle.call(self,args)
        }
    }

最后

前端小白,写出来我就是做个记忆巩固,理理思路。有不对的地方还请各位指正一下,谢谢