JS中防抖在思考(避免连续点击查询)

前几天在项目中看见防止查询屡次点击的一种写法缓存

/** * 相似于一种简单的防抖思想的实现 */
    search = () => {
        clearTimeout(this.timer);
        this.timer = setTimeout(() => {
            fetch('url').then((res) => {
                //todo
            }).catch(() => {
                //todo
            })
        } ,1000);
    }
复制代码

可是这样会存在一种问题:app

第一种状况(默认setTimeOut的时间为1秒时)

1.当用户点击一次时,其实所用的时间为接口请求时间加上1秒,当接口响应时间只有几十毫秒时,这种作法就有点鸡肋。函数

2.当用户连续点击10次时,其实前9次请求都被清除掉了(由于咱们连续点击之间的时间间隔确定会小于1秒),只有最后一次点击被执行,可是查询的时间变为了1秒加上接口请求时间fetch

第二种状况(尽可能的缩短setTimeOut的时间,好比50ms)

1.当用户点击一次,其实所用的时间为接口请求时间加上50毫秒ui

2.当用户点击10次时,假如说接口请求时间为30毫秒,而用户点击的时间间隔为200毫秒,也就是说当setTimeOut的时间加上接口请求时间小于用户点击的时间间隔时,并无限制住,其实至关于执行10次。this

我想听一下你对这种写法的理解,也不知道我对这种写法理解对不对 ?

“其实在项目开发中较经常使用的一种写法时,由于咱们大部分的查询都添加了loading效果,并且loading效果的显示是经过this.state.isLoading来进行判断的,那么这样就好判断了,点击查询时先判断this.state.isLoading是否为false,当为false时,下发请求,为true时,给出正在努力查询中的提示。”搜索引擎

再回顾一下防抖概念:url

防抖(debounce)

你是否在平常开发中遇到一个问题,在滚动事件中须要作个复杂计算或者实现一个按钮的防二次点击操做。这些需求均可以经过函数防抖动来实现。尤为是第一个需求,若是在频繁的事件回调中作复杂计算,颇有可能致使页面卡顿,不如将屡次计算合并为一次计算,只在一个精确点作操做。spa

PS:防抖和节流的做用都是防止函数屡次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的状况下只会调用一次,而节流的 状况会每隔必定时间(参数wait)调用函数。code

咱们先来看一个袖珍版的防抖理解一下防抖的实现(相似项目中防止查询屡次点击):

// func是用户传入须要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {
   // 缓存一个定时器id
   let timer = 0
   // 这里返回的函数是每次用户实际调用的防抖函数
   // 若是已经设定过定时器了就清空上一次的定时器
   // 开始一个新的定时器,延迟执行用户传入的方法
   return function(...args) {
     if (timer) clearTimeout(timer)
     timer = setTimeout(() => {
       func.apply(this, args)
     }, wait)
   }
}
// 不难看出若是用户调用该函数的间隔小于wait的状况下,上一次的时间还未到就被清除了,并不会执行函数

复制代码

其实个人理解就是防抖这种用法用户防止用户屡次点击查询好像不太特别的合适。其实它更多的场景是用于:搜索引擎搜索问题、用户放缩窗口大小等场景。

  • 上面是一个简单版的防抖,可是有缺陷,这个防抖只能在最后调用。通常的防抖会有immediate选项,表示是否当即调用: 例如在搜索引擎搜索问题的时候,咱们固然是但愿用户输入完最后一个字才调用查询接口,这个时候适用延迟执行的防抖函数,它老是在一连串(间隔小于wait的)函数触发以后调用。
  • 例如用户给interviewMap点star的时候,咱们但愿用户点第一下的时候就去调用接口,而且成功以后改变star按钮的样子,用户就能够立马获得反馈是否star成功了,这个状况适用当即执行的防抖函数,它老是在第一次调用,而且下一次调用必须与前一次调用的时间间隔大于wait才会触发。
也就是把防抖函数改为能够支持防止点击屡次查询的状况:

实现一个带有当即执行选项的防抖函数

// 这个是用来获取当前时间戳的
function now() {
  return +new Date()
}
/** * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行 * @param {function} func 回调函数 * @param {number} wait 表示时间窗口的间隔 * @param {boolean} immediate 设置为ture时,是否当即调用函数 * @return {function} 返回客户调用函数 */
function debounce (func, wait = 50, immediate = true) {
  let timer, context, args

  // 延迟执行函数
  const later = () => setTimeout(() => {
    // 延迟函数执行完毕,清空缓存的定时器序号
    timer = null
    // 延迟执行的状况下,函数会在延迟函数中执行
    // 使用到以前缓存的参数和上下文
    if (!immediate) {
      func.apply(context, args)
      context = args = null
    }
  }, wait)

  // 这里返回的函数是每次实际调用的函数
  return function(...params) {
    // 若是没有建立延迟执行函数(later),就建立一个
    if (!timer) {
      timer = later()
      // 若是是当即执行,调用函数
      // 不然缓存参数和调用上下文
      if (immediate) {
        func.apply(this, params)
      } else {
        context = this
        args = params
      }
    // 若是已有延迟执行函数(later),调用的时候清除原来的并从新设定一个
    // 这样作延迟函数会从新计时
    } else {
      clearTimeout(timer)
      timer = later()
    }
  }
}
复制代码

上面函数的实现思路:

  1. 对于按钮防点击来讲的实现:若是函数是当即执行的,就当即调用,若是函数是延迟执行的,就缓存上下文和参数,放到延迟函数中去执行。一旦我开始一个定时器,只要我定时器还在,你每次点击我都从新计时。一旦你点累了,定时器时间到,定时器重置为null,就能够再次点击了。
  2. 对于延时执行函数来讲的实现:清除定时器ID,若是是延迟调用就调用函数

其实在项目开发中我的更喜欢经过loading这种效果,来作到防止用户连续点击查询。

相关文章
相关标签/搜索