一. 应用场景
当前问题:

现在点击一个按钮,然后会向服务器请求一次数据。如果暴力操作,不断的点击按钮,那么就会一直向服务器请求,这样肯定不好,需要应用到防抖函数

最终目的:多次触发只执行最后一次或者只执行第一次

① 在点击很多次以后,我们只希望执行最后一次。
② 在点击很多次以后,我们只希望执行第一次。

二. 遇到困难

防抖函数,大家写的也够多了,最普遍的写法如下:

function debounce(fnc, delay=3000) {
  let timer = null; // 创建一个用来存放定时器的变量
  return function () {
    clearTimeout(timer); //只要触发就清除
    timer = setTimeout(() => {
      fnc.apply(this, arguments);
    }, delay);
  };
}

但是很遗憾的是,这种写法的防抖函数并没有效果。假如延迟时间设置的3000毫秒,然后我连续点击5次,表面上看起来是成功了。实际上并没有,3秒后,你传入的函数会执行5次。

不信吗?那我们写个例子来试试看呢

<template>
  <div>
	<p @click="testDebounce">测试防抖</p>
  </div>
</template>

<script>
export default {
  methods: {
    testDebounce() {
      console.log('我点击了')
      this.debounce(this.testFnc, 3000)();
    },
    testFnc() {
       console.log('执行了传入的函数testFnc');
    },
    debounce(fnc, delay=3000) {
      let timer = null; // 创建一个用来存放定时器的变量
      return function () {
        clearTimeout(timer); //只要触发就清除
        timer = setTimeout(() => {
          console.log('定时器函数执行了')
          fnc.apply(this, arguments);
        }, delay);
      };
    }
  }
}
</script>

我点击了5次,刚开始控制台没打印,3秒过后打印了5次,如下:

在这里插入图片描述

未生效原因:每次点击时,debounce里面的timer并不是共用的而是独立的,也就是每点击一次就会为当前点击创建一个timer,所以你点击5次,就生成了5个timer,这5个之间相互不会影响,那么最后打印执行了5次。

三. 解决办法

知道了原因以后就比较好解决了,在实际应用中,我们都会将防抖之类的公共函数封装在js文件里。

下面我就写在debounce.js里面,测试如下:

① 在点击很多次以后,我们只希望执行最后一次。
1.debounce.js文件里

let debounceTimer = null;
export function debounce(callback, duration) {
  return function (...args) {
    let ctx = this;
    if (debounceTimer) clearTimeout(debounceTimer);
    debounceTimer = setTimeout(() => {
      console.log('定时器函数执行了')
      callback.apply(ctx, args);
    }, duration);
  };
};

2.test组件里面

<template>
  <div>
    <p @click="testDebounce">测试防抖</p>
  </div>
</template>

<script>
import { debounce } from '../utils/debounce.js'

export default {
  methods: {
    testDebounce() {
      console.log('我点击了')
      debounce(this.testFnc, 3000)()
    },
    testFnc() {
      console.log('执行了传入的函数testFnc')
    },
  },
}
</script>

结果如下图:我连续点击了5次,只执行了最后一次。

在这里插入图片描述

② 在点击很多次以后,我们只希望执行第一次或者执行最后一次,由我们来控制。
debounce.js文件里 (例子不再贴出,根据上面的替换就行)

let debounceTimer = null;
export function debounce(callback, duration, isFirstExecution) {
  return function (...args) {
    let ctx = this;
    const delay = function () {
      debounceTimer = null;
      console.log('定时器函数执行了')
      if (!isFirstExecution) callback.apply(ctx, args);
    };
    let executeNow = isFirstExecution && !debounceTimer;
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(delay, duration);
    if (executeNow) callback.apply(ctx, args);
  };
};
// isFirstExecution为true时只执行第一次,为false时只执行最后一次

上面两个方法的关键点就是把接收定时器变量的定义提到了函数外面,变量名尽量取得特别点,不然在组件引入的时候可能有报变量名相同的风险。
(完)

---------------------------------------------------------- 补充 -----------------------------------------------------

var 声明变量时,变量会提升

let 声明变量时,存在局部作用域

所以在( 二. 遇到困难)里面的timer改为var来声明的话就不会再有上面所说的问题

function debounce(fnc, delay=3000) {
  var timer = null; // 创建一个用来存放定时器的变量
  return function () {
    clearTimeout(timer); //只要触发就清除
    timer = setTimeout(() => {
      fnc.apply(this, arguments);
    }, delay);
  };
}
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐