zoukankan      html  css  js  c++  java
  • 通俗易懂了解函数的防抖和节流

    1.前言

    在一次面试中被问到:“谈一谈js中函数的防抖和节流。”,当时菜鸡如我的内心:

    只能弱弱的说一句没怎么了解过。后来找到工作后就将这件事抛在脑后,也没在深究。

    就在前几天维护公司内部代码的时候,发现这样一个场景:当用户在创建东西时,会把用户输入的名字发往服务端校验是否重名,而当时的代码是监听了input输入框的onchange事件,只要用户一输入字符,就立即发出请求校验,这能忍?如果名字有100个字符发100次请求?用户没输完你校验个毛线啊!

    不能忍!优化!必须优化!首先想到的优化思路是:当用户输完后我再发请求校验,但是我又不知道用户什么时候输完。那么可以这样,用户一直在输入时,我不请求,当用户停止输入3秒后我就认为此时用户已经输入完成,这时候再发请求校验,这样即可大大的降低请求次数,提高性能。

    就在我沾沾自喜的拿着优化方案给Leader看的时候,Leader听完淡淡的说了一句:函数防抖和节流了解一下。

    此时回过神来,原来这就是防抖啊。

    2.概念

    函数防抖和节流,都是控制事件触发频率的方法,通常用户优化性能。

    2.1 函数防抖(debounce)最后一个人说了算

    函数防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

    函数防抖,这里的抖动就是执行的意思,而一般的抖动都是持续的,多次的。假设函数持续多次执行,我们希望让它冷静下来再执行。也就是当持续触发事件的时候,函数是完全不执行的,等最后一次触发结束的一段时间之后,再去执行。

    防抖的中心思想在于:我会等你到底。在某段时间内,不管你触发了多少次回调,我都只认最后一次。

    简单的说,当一个动作连续触发,则只执行最后一次。

    常见应用场景:

    连续的事件,只需触发一次回调的场景有:

    • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
    • 手机号、邮箱验证输入检测
    • 窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

    2.2 函数节流(throttle) 第一个人说了算

    函数节流,就是限制一个函数在一定时间内只能执行一次。

    节流的意思是让函数有节制地执行,而不是毫无节制的触发一次就执行一次。什么叫有节制呢?就是在一段时间内,只执行一次。

    节流中心思想在于:在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响应。

    常见应用场景:

    间隔一段时间执行一次回调的场景有:

    • 滚动加载,加载更多或滚到底部监听
    • 谷歌搜索框,搜索联想功能
    • 高频点击提交,表单重复提交

    2.3 直观理解

    为了方便理解,我们首先通过画图感受一下三种环境(正常情况、函数防抖情况 debounce、函数节流 throttle)下,对于mousemove事件回调的执行情况。

    竖线的疏密代表事件执行的频繁程度。可以看到,正常情况下,竖线非常密集,函数执行的很频繁。而debounce(函数防抖)则很稀疏,只有当鼠标停止移动时才会执行一次。throttle(函数节流)分布的较为均已,每过一段时间就会执行一次。

    3.代码实现

    为了说明问题,假设一个场景:鼠标滑过一个div,触发onmousemove事件,它内部的文字会显示当前鼠标的坐标。

    <style>
        #box {
           1000px;
          height: 500px;
          background: #ccc;
          font-size: 40px;
          text-align: center;
          line-height: 500px;
        }
    </style>
    
    <div id="box"></div>
    
    <script>
      const box = document.getElementById('box')
      box.onmousemove = function (e) {
        box.innerHTML = `${e.clientX}, ${e.clientY}`
      }
    </script>
    

    效果如下:

    3.1 函数防抖(debounce)

    我们想要这样的效果:当鼠标持续移动时,不显示鼠标坐标,当鼠标停止移动1秒后再显示鼠标坐标。

    分解一下需求:

    • 持续触发不执行
    • 不触发的一段时间之后再执行

    那么怎么实现上述的目标呢?我们先看这一点:在不触发的一段时间之后再执行,那就需要个定时器呀,定时器里面调用我们要执行的函数,将arguments传入。

    封装一个函数,让持续触发的事件监听是我们封装的这个函数,将目标函数作为回调(func)传进去,等待一段时间过后执行目标函数。

    function debounce(func, delay) {
      return function() {
        setTimeout(() => {
          func.apply(this, arguments)
        }, delay)
      }
    }
    

    第二点实现了,再看第一点:持续触发不执行。我们先思考一下,是什么让我们的函数执行了呢?是上边的setTimeout。OK,那现在的问题就变成了持续触发,不能有setTimeout。这样直接在事件持续触发的时候,清掉定时器就好了。

    // func是我们需要包装的事件回调, delay是每次推迟执行的等待时间
    function debounce(func, delay) {
      // 定时器
      let timeout = null;
      return function() {
        // 每次事件被触发时,都去清除之前的旧定时器,旧定时器的回调就不会执行。
        if(timer) {
            clearTimeout(timeout) 
        }
        timeout = setTimeout(() => {
          func.apply(this, arguments)
        }, delay)
      }
    }
    

    用法:

     box.onmousemove = debounce(function (e) {
        box.innerHTML = `${e.clientX}, ${e.clientY}`
      }, 1000)
    

    效果:

    说明:

    这里debounce函数执行的结果是其内部return的function的调用。也就是说鼠标经过的事件监听实际上是这个被return的function,不断持续触发的是它,而debounce函数内部用闭包声明了一个timeout的定时器,由于闭包的存在,timeout会被挂载在window对象上,每次鼠标经过,都会先清除掉上次声明的timeout,直到最后一次鼠标经过,而它的timeout没有被清除,所以最后一次的定时器才会执行。

    3.2 函数节流(throttle)

    我们想要这样的效果:当鼠标持续移动时,不显示鼠标坐标,每隔一定的时间再显示鼠标坐标。

    同样,我们再分解一下需求:

    • 持续触发并不会执行多次
    • 到一定时间再去执行

    持续触发,并不会执行,但是到时间了就会执行。抓取一个关键的点:就是执行的时机。要做到控制执行的时机,我们可以通过一个开关,与定时器setTimeout结合完成。

    函数执行的前提条件是开关打开,持续触发时,持续关闭开关,等到setTimeout到时间了,再把开关打开,函数就会执行了。

    function throttle(func, delay) {
        let run = true
        return function () {
          if (!run) {
            return  // 如果开关关闭了,那就直接不执行下边的代码
          }
          run = false // 持续触发的话,run一直是false,就会停在上边的判断那里
          setTimeout(() => {
            func.apply(this, arguments)
            run = true // 定时器到时间之后,会把开关打开,我们的函数就会被执行
          }, delay)
        }
      }
    

    用法:

    box.onmousemove = throttle(function (e) {
      box.innerHTML = `${e.clientX}, ${e.clientY}`
    }, 1000)
    

    效果:

    4.总结

    防抖和节流巧妙地用了setTimeout,来控制函数执行的时机,优点很明显,可以节约性能,不至于多次触发复杂的业务逻辑而造成页面卡顿。

    函数防抖,在一段连续操作结束后,处理回调,利用 clearTimeout 和 setTimeout 实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能。

    函数防抖关注一定时间连续触发,只在最后执行一次,而函数节流侧重于一段时间内只执行一次。
    (完)

  • 相关阅读:
    Spring Boot 使用 Dom4j XStream 操作 Xml
    Spring Boot 使用 JAX-WS 调用 WebService 服务
    Spring Boot 使用 CXF 调用 WebService 服务
    Spring Boot 开发 WebService 服务
    Spring Boot 中使用 HttpClient 进行 POST GET PUT DELETE
    Spring Boot Ftp Client 客户端示例支持断点续传
    Spring Boot 发送邮件
    Spring Boot 定时任务 Quartz 使用教程
    Spring Boot 缓存应用 Memcached 入门教程
    ThreadLocal,Java中特殊的线程绑定机制
  • 原文地址:https://www.cnblogs.com/wangjiachen666/p/11212992.html
Copyright © 2011-2022 走看看