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 实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能。

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

  • 相关阅读:
    python编写规范
    我们分析了400位华语歌手的歌词,发现人们重点关注的人事物情
    外部厂商公开工具
    OSI七层与TCP/IP五层网络架构详解
    npm run dev--The 'mode' option has not been set, webpack will fallback to 'production' for this value
    webpack-dev-server --inline --progress --config build/webpack.dev.conf.js
    PPTV(pplive)_forap_1084_9993.exe 木马清除经历
    【转】【Nginx】Nginx 入门教程 + 常用配置解析
    【转】【Python】Python 中文编码报错
    【Centos】systemd入门教程
  • 原文地址:https://www.cnblogs.com/wangjiachen666/p/11212992.html
Copyright © 2011-2022 走看看