手写一次防抖与节流,就懂了

4,084 阅读3分钟

概念

1. 防抖(Debounce)

函数防抖,就是指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间。

通俗的讲: 一段时间内重复触发,只执行开始一次和结尾一次,或者只执行结尾那次

使用场景:浏览器页面onresizescrollmousemove ,mousehover 等,会被频繁触发(短时间内多次触发)的时候就需要用到防抖,或者某些点击事件,防止多次点击造成频繁请求后台。

2. 节流(Throttle)

函数节流,就是指触发事件后,在n秒内函数只能执行一次,如果触发事件后在n秒内又触发了事件,则会再次执行该事件。顾名思义,就是像水管流水一样,有频率的执行。

通俗的讲: 一段时间内重复触发,按一定频率(3s、5s)执行,可配置一开始就执行一次

使用场景:浏览器页面onresizescrollmousemove ,mousehover 等,会被频繁触发(短时间内多次触发)的时候就需要用到节流,例如浏览器发生滚动事件时,每3秒或5秒执行一次事件。

使用

1. 手写防抖函数

编写前思考:

  1. debounce是一个函数,并且需要传入一个函数handle
  2. 传入函数后,我们要传入一个防抖时间wait
  3. 需要判断是否立即执行immediate
  4. 返回一个一个函数 fn

编写

function debounce(handle,wait,immediate){
    //首先进行参数判断
    if(typeof handle !== 'function') throw new Error('handle must be function')
    //默认wait为1s
    if(typeof handle === 'undefinde') wait = 1000 
    //如果只传入 handle 和 immediate
    if(typeof wait === 'boolean'){
        immediate = wait
        wait = 1000
    }
    //immediate默认为flase
    if(typeof immediate !== 'boolean') immediate = false
    //所谓的防抖效果,我们想要实现的就是一个人为可以管理handle的执行次数
    let timer = null
    return function proxy(...args){
        let self = this,
        init = immediate && !timer
        clearTimeout(timer)
        timer = setTimeout(()=>{
            timer = null
            !immediate ? handle.aplly(self,args) : null
        },
        wait)
        //如果立即执行
        init ? handle.apply(self,args) : null
    }
}

HTML中使用

<button type="button" id="btn1">防抖</button>
//点击函数
function clickHandle(){
    console.log('防抖演示')
}
document.getElementById('btn1').addEventListener('click',debounce(clickHandle,3000,true))

VUE中使用

<button type="button" @click="clickHandle">防抖</button> 

//method方法中
methods:{
    clickHandle: debounce(function(){
        console.log('防抖演示')
    },3000,true)
}

手写节流函数

编写前思考:

  1. debounce是一个函数,并且需要传入一个函数handle
  2. 传入函数后,我们要传入一个节流时间wait
  3. 需要判断是否立即执行immediate
  4. 返回一个一个函数 fn

编写

//通过时间差实现
function throttle(handle,wait,immediate){
    if (typeof handle !== 'function') throw new Error('handle must be an function')
    if (typeof wait === 'undefined') wait = 1000
    //如果只传入 handle 和 immediate
    if(typeof wait === 'boolean'){
        immediate = wait
        wait = 1000
    }
    //immediate默认为flase
    if(typeof immediate !== 'boolean') immediate = false
    //定义变量记录上一次执行的时间
    let previous = 0
    let timer = null
    return function proxy(...args){
        //获取当前时间
        let now = new Date()
        let self = this
        //如果不立即执行
        if(!immediate) previous = now
        // 计算间隔时间
        let interval = wait - (now - previous)
        if(interval <= 0){
            // 此刻就说明是一个非高频次操作,可以执行操作
            clearTimeout(timer)
            timer = null
            handle.apply(self,args)
            previous = new Date()
        }else if(!timer){
            //当我们发现系统中有一个定时器了,就意味着我们不需要再开定时器
            //此刻就说明这次的操作发生了在我们定义的频次时间范围内,那就不应该执行handle
            // 这个时候我们就可以自定义一个定时器,让handle在interval之后去执行
            timer = setTimeout(()=>{
                clearTimeout(timer) //清除定时器
                timer = null
                handle.apply(self,args)
                previous = new Date()
            },interval)
        }
    }
}
//通过定时器实现(简洁版)
function throttle(fn, wait = 1000, immediate = false) {
  let flag = true
  let timer = null
  return function(...args) {
    if (flag) {
      //是否立即执行
      immediate && fn.apply(this, args)
      //关闭通道,等待定时器执行完后再开启
      flag = false
      timer = setTimeout(() => {
        !isImmediate && fn.apply(this, args)
        flag = true
      }, wait)
    }
  }
}

使用

节流的使用和防抖一样,参考上面防抖得到使用

总结

防抖和节流在日常开发中经常会遇到,建议将其封装到项目的功能函数中。特别是点击提交按钮时,为了防止用户误点提交多次,可以在内部加一个1s左右的防抖函数。