防抖与节流

4/22/2024 随便

# 防抖

当持续触发事件时,一定时间段内没有再触发事件,函数才会执行一次,如果在这个时间段内又触发了事件,则会重新开始延时。常用于输入框搜索、滚动加载等场景。

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

# 节流

在一定时间范围内,用户触发多次只会执行一次以达到防止用户频繁操作的目的。

let timer = null;

function throttle(fn, delay = 300){
  if(timer === null) {
    timer = setTimeout(()=>{
      fn()

      clearTimeout(timer)
      timer = null
    }, delay)
  }
}

不过我更多的是在请求之前使界面的 loading 变为 true,然后在请求结束后变为结束 loading。

# 实际需求

后端每发一次ws信息,都可能导致前端界面发送资源请求来刷新界面的数据,但是后端发送的频率太快了,有点任务执行的很快,会一秒发七八次。因此需要封装一下请求,达到以下需求:

  • 在正常情况下,收到ws消息发送资源请求,随后进入锁模式
  • 锁模式会在三秒后自动解锁
  • 如果在锁模式的三秒内又收到ws消息,进阶到 后置请求锁模式
  • 解锁时,
    • 如果是锁模式,正常解锁
    • 如果是后置请求锁模式,还需额外请求一次,并退阶到锁模式
export class ApiThrottle {
  static NORMAL = 'normal' // 正常状态
  static LOCK = 'lock' // 首次收到api请求后进入,默认锁三秒钟,期间收到api请求不会触发,会变为LOCK_END_API,一秒后自动变为NORMAL
  static LOCK_END_API = 'lock_end_api' // 锁结束时,如果是当前状态,还会解锁后再请求一遍

  constructor (callback, lockTime = 3000) {
    this.flag = ApiThrottle.NORMAL
    this.callback = callback
    this.lockTime = lockTime
  }

  // 收到消息了,触发此函数
  getRequest () {
    // 正常情况下,立即触发一次请求,进入锁模式,设定三秒后进入fresh方法,自动解锁
    if (this.flag === ApiThrottle.NORMAL) {
      this.flag = ApiThrottle.LOCK
      this.callback()
      this.timeOut = setTimeout(() => { this.fresh() }, this.lockTime)
    } else if (this.flag === ApiThrottle.LOCK) {
      // 锁模式下进阶
      this.flag = ApiThrottle.LOCK_END_API
    }
  }

  fresh () {
    if (this.flag === ApiThrottle.LOCK) {
      this.flag = ApiThrottle.NORMAL
    } else if (this.flag === ApiThrottle.LOCK_END_API) {
      // 进阶模式,那就需要额外请求一次,并退阶
      this.flag = ApiThrottle.LOCK
      this.callback()
      this.timeOut = setTimeout(() => { this.fresh() }, this.lockTime)
    }
  }

  // 界面销毁时删除一切资源
  destory () {
    clearTimeout(this.timeOut)
    delete this
  }
}

# 使用方法(vue2)

{
  data(){
    apiThrottle: undefined,
  },
  mounted () {
    this.apiThrottle = new ApiThrottle(() => { this.loadData(this.order) })
    // 注册ws监听
    this.$options.sockets.onmessage = data => {
      const msg = JSON.parse(data.data)
      if (msg.purpose === 'task') this.apiThrottle.getRequest()
    }
  },
  // 销毁资源
  unmounted () {
    delete this.$options.sockets.onmessage
    this.apiThrottle.destory()
  }
}