防抖节流

220 阅读5分钟

防抖节流

  1. 什么是防抖节流?

防抖(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.apply(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.apply(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.apply(ctx, args)

    } else {

      timer = setTimeout(() => {

        fn.apply(ctx, args)

      }, wait)

    }

  }

}

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

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

场景:

  1. scroll 事件,每隔一秒计算一次位置信息等
  2. 浏览器播放事件,每隔一秒计算一次进度信息等
  3. input 框实时搜索并发送请求展示下拉列表,没隔一秒发送一次请求 (也可做防抖)
//定时器版:函数不会立即执行,并且每x秒执行一次,在停止触发事件后,函数还会再执行一次

function throttle(fn, time) {

      let timeout;

      return function () {

          if (!timeout) {

              timeout = setTimeout(() => {

                  fn.apply(this, arguments)

                  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){

        var previous = 0;

    } else if(type===2){

        var 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项目中的使用方式

  1. 自定义指令方式
//结合第三方库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)

  }

}
  1. 引用第三方库( lodash)

文档链接:www.lodashjs.com/

防抖函数:_.debounce(func, [wait=0], [options=])#

创建一个 debounced(防抖动)函数,该函数会从上一次被调用后,延迟 wait 毫秒后调用 func 方法。 debounced(防抖动)函数提供一个 cancel 方法取消延迟的函数调用以及 flush 方法立即调用。 可以提供一个 options(选项) 对象决定如何调用 func 方法,options.leading 与|或 options.trailing 决定延迟前后如何触发(注:是 先调用后等待 还是 先等待后调用)。 func 调用时会传入最后一次提供给 debounced(防抖动)函数 的参数。 后续调用的 debounced(防抖动)函数返回是最后一次 func 调用的结果。

注意: 如果 leading 和 trailing 选项为 true, 则 func 允许 trailing 方式调用的条件为: 在 wait 期间多次调用防抖方法。

如果 wait 为 0 并且 leading 为 false, func调用将被推迟到下一个点,类似setTimeout为0的超时

参数

  1. func (Function): 要防抖动的函数。
  2. [wait=0] (number): 需要延迟的毫秒数。
  3. [options=] (Object): 选项对象。
  4. [options.leading=false] (boolean): 指定在延迟开始前调用。
  5. [options.maxWait] (number): 设置 func 允许被延迟的最大值。
  6. [options.trailing=true] (boolean): 指定在延迟结束后调用。

返回

(Function): 返回新的 debounced(防抖动)函数。

vue项目中使用

  1. 安装依赖

npm i --save lodash

  1. 按需引入

import _debounce from 'lodash/debounce

  1. 调用
//点击之后立马调用,一秒内只打印一次

getList: _debounce(

   function () { //这里不能用箭头函数  因为箭头函数没有自身的this

    console.log("debounce");

  },

  5000,

  { leading: true, trailing: false }

)

PS:lodash默认leadingfalsetrailingtrue,那么最终的效果是在点击后等待5秒才会打印debounce,即延迟之前不执行函数,而是在延迟之后执行。
handleClick: _debounce(

   function(type) {

      console.log('_debounce')

      this.$emit('getClickResult', type)

    },

    500,

    { leading: true, trailing: false }

),