防抖节流及在Vue里面的应用

1,827 阅读3分钟

同事分享的防抖节流及在项目上的应用,觉得还可以,就转发过来了~~~

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

防抖节流

什么是防抖节流?

防抖(Debounce):防止重复点击触发事件(最后一个人说了算,在某段时间内,不管你触发了多少次回调,我都只认最后一次)

应用场景:

  1. 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
  2. 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
  3. 文本编辑器实时保存,当无任何更改操作一秒后进行保存
  4. ...

代码实现要点:设置一个定时器,通过闭包,抓住定时器变量,控制定时器的添加和删除

//非立即执行版:触发事件后函数不会立即执行,而是在n秒后执行,如果在n秒内又触发了事件,则会重新计算函数执行时间

function debounce(fn, time) {
      let timer
      return function () {
          const args = arguments
          if (timer) clearTimeout(timer)
          timer = setTimeout(() => {
              fn.call(this, args)
          }, time)
      }
  }



// 立即执行版:触发事件后函数会立即执行,n秒内不触发事件才能继续执行函数的效果

function debounce(fn, time) {
  let timer;
  return function () {
    const ctx = this;
    const args = arguments;
    let callNow = !timer;
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      timer = null;
    }, time)

    if (callNow) fn.call(ctx, args);
  };
}



//结合版本

/**
 * @desc 函数防抖
 * @param fn 函数
 * @param wait 延迟执行毫秒数
 * @param immediate true 表立即执行,false 表非立即执行
 */

function debounce (fn, wait, immediate) {
  let timer
  return function () {
    const ctx = this
    const args = arguments
    if (timer) clearTimeout(timer)
    // 立即执行
    if (immediate) {
      const callNow = !timer
      timer = setTimeout(() => {
        timer = null
      }, wait)
      if (callNow) fn.call(ctx, args)
    } else {
      timer = setTimeout(() => {
        fn.call(ctx, args)
      }, wait)
    }
  }
}

节流(Throttle):指定时间间隔内只会执行一次任务(在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响应)

代码实现要点:主要分定时器版和时间戳版

场景:

  1. scroll 事件,每隔一秒计算一次位置信息等
  2. 浏览器播放事件,每隔一秒计算一次进度信息等
  3. input 框实时搜索并发送请求展示下拉列表,没隔一秒发送一次请求 (也可做防抖)
//定时器版:函数不会立即执行,并且每x秒执行一次,在停止触发事件后,函数还会再执行一次
function throttle(fn, time) {
      let timeout;
      return function () {
          const args = arguments
          if (!timeout) {
              timeout = setTimeout(() => {
                  fn.call(this, args)
                  timeout = null
              }, time);
          }
      }
  }


//时间戳版: 在持续触发事件的过程中,函数会立即执行,并且每x秒执行一次

function throttle(fn, time) {
    let previous = 0;
    return function() {
        let now = Date.now();
        let context = this;
        let args = arguments;
        if (now - previous > time) {
            fn.apply(context, args);
            previous = now;
        }
    }
}



//结合版
/**
 * @desc 函数节流
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param type 1 表时间戳版,2 表定时器版
 */

function throttle(fn, time ,type) {
    if(type===1){
        let previous = 0;
    } else if(type===2){
        let timeout;
    }

    return function() {
        let context = this;
        let args = arguments;
        if(type===1){
            let now = Date.now()
            if (now - previous > time) {
                fn.apply(context, args);
                previous = now;
            }
        }else if(type===2){
            if (!timeout) {
                timeout = setTimeout(() => {
                    timeout = null;
                    fn.apply(context, args)
                }, time)
            }
        }
    }
}
  1. 在vue项目中的使用方式

自定义指令方式

//结合第三方库Lodash

/**

 * 防抖节流

 * 1.根据修饰符判断是防抖还是节流

 * 使用方法如下

 * (1)v-debounceOrThrottle.debounce   防抖

 * (2)v-debounceOrThrottle.throttle   节流

 * 2.根据传入参数wait、leading、trailing 决定 wait 前后如何触发(可不传使用默认值)

 * 使用方法如下

 * (1)v-debounceOrThrottle:{wait: 2000,leading:false,trailing:true}.debounce

 * @params wait 延迟时间

 * @params leading:true(false) 指定调用在[防抖/节流]开始之前 (后)

 * @params trailing: true(false) 指定调用在[防抖/节流]开始之后(前)

 */

import _debounce from 'lodash/debounce'
import _throttle from 'lodash/throttle'
let fn = null

export default {
  inserted: function(el, binding) {
    const { modifiers, arg } = binding
    const time = 2000
    let _arg = {}
    if (arg) _arg = eval('(' + arg + ')')
    const { wait = time, leading = true, trailing = false } = _arg
    if (modifiers.throttle) {
      // 节流
      fn = _throttle(binding.value, wait, {
        leading,
        trailing
      })
    } else {
      // 防抖
      fn = _debounce(binding.value, wait, {
        leading,
        trailing
      })
    }
    el.addEventListener('click', fn)
  },

  //unbind: 只调用一次,指令与元素解绑时调用
  unbind: function(el) {
    fn && el.removeEventListener('click', fn)
  }
}