前端--click单双击问题及函数节流与防抖问题

不折腾的前端,和咸鱼有什么区别

本篇博客,我们将探讨2个问题,click单双击问题及函数节流与防抖问题,其实都是关于click事件的探讨,当我们对一个按钮进行单击或者双击,或者多次点击,当我们频繁触发click事件时,这时就涉及到我们的函数节流与防抖问题。

  • 单双击(仿双击)

在js中,同一个功能块可能会遇到单击、双击事件两种事件,当我们执行双击事件时,同时也执行了2次单击事件。任何避免执行双击事件时,不执行单击事件。这时我们需要对单击事件进行延时,如果在此延时中又监测到单击事件,那么认为此两次单击属于一个双击事件,则只执行双击事件,并第一时间将延时定时器清理,以防止第二次单击生效。
具体代码如下:

方法一:
对单击事件进行延迟执行

var clickFlag = null;//是否点击标识(定时器编号)
function doOnClick(...) {
    if(clickFlag) {//取消上次延时未执行的方法
        clickFlag = clearTimeout(clickFlag);
    }
    
    clickFlag = setTimeout(function() {
        // click 事件的处理
    }, 300);//延时300毫秒执行
}

function doOnDblClick(...) {
    if(clickFlag) {//取消上次延时未执行的方法
        clickFlag = clearTimeout(clickFlag);
    }
    
    // dblclick 事件的处理
}

方法二:
利用仿双击原理

var lastClick = 0;
function doClick(...){
   var timeNow = new Date().getTime();
   if(timeNow - lastClick < 500){
     // this.doDbclick();   //执行双击事件函数
     this.lastClick = 0;
     return;
   }
   this.lastClick = timeNow;
   //  执行单击事件代码
}
  • 函数节流与防抖(防止多次点击、重复点击、或点击过快)

函数节流与函数防抖是我们解决频繁触发DOM事件的两种常用解决方案。DOM操作比起非DOM交互,需要更多的内存和CPU时间,连续尝试过多的DOM操作可能会导致浏览器挂起,甚至崩溃。

一:函数节流

概念:指定时间间隔内只会执行一次任务。若有新事件触发,不执行。周期结束后,又有事件触发,开始新的周期。

举个栗子:一个水龙头在滴水,可能一次性会滴很多滴,但是我们只希望它每隔 500ms 滴一滴水,保持这个频率。即我们希望函数在以一个可以接受的频率重复调用。

实现方式:使用定时器对函数进行节流,即第一次调用函数,创建一个定时器,在指定的时间间隔后运行代码。当第二次调用该函数的时候,它会清除前一次的定时器并设置另外一个。

代码:

// 函数节流 throttle
// 方法一:定时器实现
const throttle = function(fn,delay) {
  let timer = null

  return function() {
    const context = this
    let args = arguments
    if(!timer) {
      timer = setTimeout(() => {
        fn.apply(context,args) 
        clearTimeout(timer) 
      },delay)
    }
  }
}

// 方法二:时间戳
const throttle2 = function(fn, delay) {
  let preTime = Date.now()

  return function() {
      const context = this
      let args = arguments
      let doTime = Date.now()
      if (doTime - preTime >= delay) {
          fn.apply(context, args)
          preTime = Date.now()
      }
  }
}

二:函数防抖

概念:当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。

举个栗子: 将一个弹簧按下,继续加压,继续按下,只会在最后放手的一瞬反弹。即我们希望函数只会调用一次,即使在这之前反复调用它,最终也只会调用一次而已。

实现方式:将若干函数调用合成为一次,并在给定时间过去之后,或者连续事件完全触发完成之后,调用一次(仅仅只会调用一次!!!!!!!!!!)。

代码:

// 给盒子较大的height,容易看到效果
<style>
    * {
        padding: 0;
        margin: 0;
    }

    .box {
        width: 800px;
        height: 1200px;
    }
</style>
<body>
    <div class="container">
        <div class="box" style="background: tomato"></div>
        <div class="box" style="background: skyblue"></div>
        <div class="box" style="background: red"></div>
        <div class="box" style="background: yellow"></div>
    </div>
    <script>
        window.onload = function() {
            const decounce = function(fn, delay) {
                let timer = null

                return function() {
                    const context = this
                    let args = arguments
                    clearTimeout(timer) // 每次调用debounce函数都会将前一次的timer清空,确保只执行一次
                    timer = setTimeout(() => {
                        fn.apply(context, args)
                    }, delay)
                }
            }

            let num = 0

            function scrollTap() {
                num++
                console.log(`看看num吧 ${num}`)
            }
            // 此处的触发时间间隔设置的很小
            document.addEventListener('scroll', decounce(scrollTap, 500))
            // document.addEventListener('scroll', scrollTap)
        }
    </script>
</body>

本篇博客写的有点匆忙,有时间我还会修改的。

有兴趣的可以去了解一下“重绘与回流”

拓展一:

有时候我们点击左侧或者顶部导航时,或者点击某一按钮时。不希望一定时间内,请求后端太多次,此时我们可以写个封装函数,限制一定时间内点击次数

/*限制一定时间内点击次数*/
const clickTime: any = {};
export function clickOnce(key: any = 'global', time = 500): any {
  return (target: object, propertyKey: string, descriptor?: TypedPropertyDescriptor<any>) => {
    if (descriptor === undefined) {
      descriptor = Object.getOwnPropertyDescriptor(target, key);
    }
    const orgFunc = descriptor.value;
    function click(...args) {
      let dataKey = key;
      const self = this;
      if (typeof key === 'function') {
        dataKey = key.apply(self, args);
      }
      const lastTime = clickTime[dataKey];
      if (!lastTime || Date.now() - lastTime > time) {
        clickTime[dataKey] = Date.now();
        return orgFunc.apply(self, args);
      }
      return false;
    }
    descriptor.value = click;
    return descriptor;
  };
}

拓展二:

列表页search搜索时,当我们输入关键字后,快速频繁点击搜索button时,此时也会不断请求后端接口,为了避免快速点击不断请求后端接口而造成的资源浪费及其他一系列问题,此时我们可以用拓展一的方法“限制一定时间内请求的次数”,也可以用如下方法:
实现原理:当同一时间内,对搜索按钮进行第二次、第三次…第n次点击时,判断搜索条件是否发生变化,如果搜索条件没发生变化,则不请求后端接口,如果发生变化则发送请求。

在CommonFuncService.ts 这个ts文件里写如下封装函数:

/**封装函数,对比数据是否有发生变化*/

    public static objectEq(object1, object2) {
        if (object1 instanceof Array) {
            if (!(object2 instanceof Array)) {
                return false;
            }
            if (object1.length != object2.length) {
                return false;
            }
            let len = object1.length;
            for (let i = 0; i < len; ++i) {
                if (!CommonFuncService.objectEq(object1[i], object2[i])) {
                    return false;
                }
            }
            return true;
        } else if (object1 && typeof object1 == 'object') {
            if (typeof object2 != 'object') {
                return false;
            }
            if (Object.getOwnPropertyNames(object1).length != Object.getOwnPropertyNames(object2).length) {
                return false;
            }
            for (let name in object1) {
                if (!CommonFuncService.objectEq(object1[name], object2[name])) {
                    return false;
                }
            }
            return true;
        }
        return object1 === object2;
    }

页面引用

import { CommonFuncService } from 'app/shared'; 

public oldSearch;

// 搜索
   querySubmit() {
        const searchParam = this.queryData();   // this.queryData() 是要搜索的参数
        if (!this.oldSearch || !CommonFuncService.objectEq(this.oldSearch, searchParam)) {
            this.OnQueryChange.emit(this.queryData());
            this.oldSearch = Object.assign({}, searchParam);  // 不要把searchParam直接赋值给oldSearch ,以防数据发生变化时,同时变化,判断出错
           // this.oldSearch = searchParam; 
        }
    }

参考: 函数节流与函数防抖的区别

你可能感兴趣的:(click单双击,函数节流与防抖)