如何理解debounce(防抖)和throttle(节流)?

  • 节流 像阀门一样控制水流,避免单位时间内流量过大
  • 防抖 防止抖动,比节流的流量控制效果更佳明显

在做远程搜索时,如果每输入1个字就调用1次接口,就会频繁查询数据库,假设我们的查询是"12345",不考虑用户输入错误的情况,至少会请求5次。

再思考一个问题,按钮的click重复触发(例如快速点击2次,3次,...n次)该如何在前端做一层拦截,避免发送重复请求到服务端,最常见的是新增时插入重复数据,这个问题该怎么办呢?

  • 查询是"12345",至少会请求5次导致频繁查询数据库
  • 有没有一种方法,可以隔个几百毫秒再去查询呢?(setTimeout)
  • 有没有更加高级的做法,用户输入完成后,停顿了几百毫秒再去查询呢?(debounce)
  • 有没有用户体验更加好的做法,不用等待漫长的等待时间从而响应更快呢?(throttle)
  • 快速点击2次,3次,...n次新增按钮导致插入重复数据
  • 如何避免用户单位时间内频繁点击按钮导致重复发送请求的问题?(debounce)
  • 有没有除了debounce之外的更加精准的方法?(loading)
  • debounce适用场景
  • throttle适用场景
  • debounce和throttle的对比
  • 手写一个debounce和throttle(setTimeout版本)
  • 手写一个throttle(不能使用setTimeout)

    查询是"12345",至少会请求5次导致频繁查询数据库

    <template>
      <input @input="handleInput"/>
    </template>
    
    <script>
    export default {
      name: 'input',
      data() {
        return {
          delay: 1000,
          count: 0,
        };
      },
      methods: {
        handleInput(e) {
          console.log(`debounce wait时间为${this.delay}ms`);
          console.log('触发了input事件', e.target.value);
          this.count++;
          console.log(`触发了${this.count}次远程搜索`);
        },
      },
    };
    </script>

    打印结果:

    debounce wait时间为1000ms 触发了input事件 1 触发了1次远程搜索

    debounce wait时间为1000ms 触发了input事件 12 触发了2次远程搜索

    debounce wait时间为1000ms 触发了input事件 123 触发了3次远程搜索

    debounce wait时间为1000ms 触发了input事件 1234 触发了4次远程搜索

    debounce wait时间为1000ms 触发了input事件 12345 触发了5次远程搜索

    说明:输入5个数查询5次,造成了频繁查询数据库的行为,是一种性能浪费。

    <template>
      <input @input="debounceHandleInput"/>
    </template>
    
    <script>
    import _ from 'lodash';
    
    export default {
      name: 'input-debounce',
      data() {
        return {
          delay: 1000,
        };
      },
      computed: {
        debounceHandleInput() {
          return _.debounce(this.handleInput, this.delay);
        },
      },
      methods: {
        handleInput(e) {
          console.log(`debounce wait时间为${this.delay}ms`);
          console.log('触发了input事件', e.target.value);
          this.count++;
          console.log(`触发了${this.count}次远程搜索`);
        },
      },
    };
    </script>

有没有一种方法,可以隔个几百毫秒再去查询呢?(setTimeout)

有,可以为函数设置一个setTimeout函数,相当于定时调用接口,这种方法是低效的,也是非常愚蠢的,需要控制开关定时器,一旦搜索功能多了,就更蠢了。

有没有更加高级的做法,用户输入完成后,停顿了几百毫秒再去查询呢?(debounce)

有,debounce(防抖)就是做这个事情的,lodash从0.1.0就支持了这个方法。

<template>
  <input @input="debounceHandleInput"/>
</template>

<script>
import _ from 'lodash';

export default {
  name: 'input-debounce',
  data() {
    return {
      delay: 1000,
    };
  },
  computed: {
    debounceHandleInput() {
      return _.debounce(this.handleInput, this.delay);
    },
  },
  methods: {
    handleInput(e) {
      console.log(`debounce wait时间为${this.delay}ms`);
      console.log('触发了input事件', e.target.value);
      this.count++;
      console.log(`触发了${this.count}次远程搜索`);
    },
  },
};
</script>

打印结果: debounce wait时间为1000ms 触发了input事件 12345

说明:在1000ms时间范围内触发,仅仅触发了一次远程搜索,也就是仅仅调用一次后端接口,达到我们的预期效果。

有没有用户体验更加好的做法,不用等待漫长的等待时间从而响应更快呢?(throttle)

<template>
  <input @input="throttleHandleInput"/>
</template>

<script>
import _ from 'lodash';

export default {
  name: 'input-throttle',
  data() {
    return {
      delay: 1000,
      count: 0,
    };
  },
  computed: {
    throttleHandleInput() {
      return _.throttle(this.handleInput, this.delay);
    },
  },
  methods: {
    handleInput(e) {
      console.log(`throttle wait时间为${this.delay}ms`);
      console.log('触发了input事件', e.target.value);
      this.count++;
      console.log(`触发了${this.count}次远程搜索`);
    },
  },
};
</script>

打印结果:

throttle wait时间为1000ms 触发了input事件 1 触发了1次远程搜索

throttle wait时间为1000ms 触发了input事件 12345 触发了2次远程搜索

说明:在1000ms时间范围内触发,仅仅触发了2次远程搜索,调用2次后端接口。用户首次输入1立即返回数据,保证数据到达速度,也提升了用户体验。中间的12,123,1234被节流函数成功拦截避免触发。而12345是我们最终需要的搜索结果,在最后返回给用户。达到我们的预期效果


 快速点击2次,3次,...n次新增按钮导致插入重复数据

<template>
  <button @click="handleClick">新增</button>
</template>

<script>
export default {
  name: 'click',
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    handleClick(e) {
      console.log('触发了click事件', e.target.value);
      this.count++;
      console.log(`触发了${this.count}次新增数据`);
    },
  },
};
</script>

触发了click事件 触发了1次新增数据 触发了click事件 触发了2次新增数据

说明:快速点击2次“新增”按钮,而最终只触发了2次数据新增,造成重复数据插入。

如何避免用户单位时间内频繁点击按钮导致重复发送请求的问题?(debounce)

 

<template>
  <button @click="debounceHandleClick">新增</button>
</template>

<script>
import _ from 'lodash';

export default {
  name: 'click-debounce',
  data() {
    return {
      delay: 1000,
      count: 0,
    };
  },
  computed: {
    debounceHandleClick() {
      return _.debounce(this.handleClick, this.delay);
    },
  },
  methods: {
    handleClick(e) {
      console.log(`debounce wait时间为${this.delay}ms`);
      console.log('触发了click事件', e.target.value);
      this.count++;
      console.log(`触发了${this.count}次新增数据`);
    },
  },
};
</script>

打印结果: debounce wait时间为1000ms 触发了click事件 触发了1次新增数据

说明:快速点击2次“新增”按钮,而最终只触发了1次数据新增,达到了我们的预期效果。

有没有除了debounce之外的更加精准的方法?(loading)

loading是指在异步请求完成(成功或者失败)前,开启loading,使得按钮或者用户界面处于“加载中”“转圈”“spin"这样的一个状态,从而禁止用户发起重复操作,异步请求完成后,关闭loading。

<template>
  <Button @click="loadingHandleClick" :loading="loading">新增</Button>
</template>

<script>
export default {
  name: 'click-loading',
  data() {
    return {
      loading: false,
      count: 0,
    };
  },
  methods: {
    loadingHandleClick(e) {
      this.loading = true;
      this.count++;
      console.log(`触发了${this.count}次新增数据`);
      console.log('发起异步请求,loding为:', this.loading);
      setTimeout(() => {
        console.log('异步请求执行了1s');
        this.loading = false;
        console.log('异步请求完成,loding为:', this.loading);
      }, 1000);
    },
  },
};
</script>

触发了1次新增数据 发起异步请求,loding为: true 异步请求执行了1s 异步请求完成,loding为: false

说明:第1次事件触发后,按钮处于loading为true状态,禁止用户触发,1秒后异步请求执行完成,loading为false,允许用户再次使用。因此都无法做到快速点击2次“新增”按钮,只能触发1次,重复的第2次触发用户无法触发,最终只触发了1次数据新增,达到了我们的预期效果。

手写一个debounce

function debounce(fn, delay){
    let timer;
    return function(){
        const _this = this;
        const args = arguments;
        if(timer) clearTimeout(timer);
        timer = setTimeout(()=>{
            fn.apply(_this, args); // _this.fn(args);
        }, delay)
    }
}

手写一个throttle

function throttle(fn, delay){
    let timer;
    return function(){
        const _this = this;
        const args = arguments;
        if(timer) return;
        timer = setTimeout(()=>{
            fn.apply(_this, args); // _this.fn(args);
            timer = null;
        }, delay)
    }
}

手写一个throttle(不能使用setTimeout)

function throttle(fn, delay){
    let lastTime = 0;
    return function(){
        const _this = this;
        const args = arguments;
        const currentTime = Date.now();
        if(currentTime - lastTime > delay){
            fn.apply(_this, args);
            lastTime = currentTime
        }
    }
}
let fn = (e,data)=>{console.log(e, e.offsetX, e.offsetY, data)}
let throttleFn = throttle(fn, 2000)
document.onmousemove = function(e){
    throttleFn(e, 'throttle')
}

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

勒布朗-前端

请多多支持,留点爱心早餐

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值