节流防抖的应用与实现

596 阅读2分钟

节流防抖适合的场景

  • 多次点击发送验证码按钮,多次点击提交按钮,多次点击支付按钮等情况
  • 监听了resize、scroll等事件,触发频率太高
  • 类似百度那种input搜索框输入关键字实时显示联想的热门关键词

说白了就是限制函数的执行频率,减少不必要的资源、接口请求或者dom操作

两者比较

节流:单位时间内函数只执行一次(函数多次触发只执行第一次)

防抖:单位时间内函数多次触发都会被重置,延迟一段时间过后执行一次(函数多次触发只会延时执行最后一次)

节流实现

方法一:时间戳

function throttle(fn, delay) {
  let previous = 0 // 上一次触发的时间戳, 初始为0, 确保第一次触发产生回调
  return (...args) => {
      let now = Date.now() // 当前触发时的时间戳
      let that = this
      if (now - previous > delay) {  // 如果时间差大于规定时间, 则触发函数
          fn.call(that, ...args)
          previous = now // 更新上一次触发时间
      }
  }
}

方法二:定时器

function throttle (fn, delay) {
  let timer
  return (...args) => {
    if (timer) return // 若存在定时器,说明已有函数正在等待执行,直接返回,不触发重复执行
    timer = setTimeout(() => {
      fn.call(this, ...args)
      timer = null
    }, delay)
  }
}

防抖实现

一般通过清除timer定时器对象来实现

function debounce(fn, delay) { 
  let timer = null  // 声明一个变量存储定时器
  return (...args) => {
      timer && clearTimeout(timer)  // 重复触发,清除定时器
      let that = this
      timer = setTimeout(() => { // 重新定义定时器,重新开始计时
          fn.call(that, ...args)
      }, delay)
  }
}

上面的代码也算实现了一个防抖函数,但是有些场景可能会有问题。假设我们给支付按钮设置了防抖,设置了delay为500ms,当用户不断重复点击支付按钮时,可能点击间隔小于500ms,这样就会导致用户一直在点击,定时器一直在重置,页面没有任何反应,用户可能以为页面卡死。所以这个时候我们就需要第一次点击支付按钮的时候,先立即执行一次,后续的点击按照delay延迟执行

 // immediate:是否第一次点击立即执行
 function debounce(fn, delay, immediate) {
   let timer = null
   let flag = false // flag变量用来记录是否执行过第一次,默认false

   return (args) => {
     timer && clearTimeout(timer) // 先清除定时器
     // 需要立即执行,且是第一次触发
     if (immediate && !flag) {
       fn.apply(this, args) // 立即执行
       flag = true // 将flag设置为true,这样后面频繁触发会去设置定时器
       return
     }

     timer = setTimeout(() => {
       fn.apply(this, args)
       flag = false // 将flag设为false,这样下一次的第一次触发也可以被立即执行
     }, delay)
   }
 }