防抖和节流使用场景模拟及源码解析

85 阅读4分钟

前言

居家办公的日子里,远离人群和喧嚣,也远离了技术的交流,有的时候感觉找不到方向,是追求知识的广度还是知识的深度,之前写的文章,现在也会用到,有的时候看看觉得有些遗忘,有的时候会质疑自己学了忘,学了忘,可却忘了,人本来就是会遗忘,只有重复和持续的学习才能保持头脑的清醒。

阳了的日子,嗓子也说不出话来,叫的外卖,过了时间,我打电话,咿咿呀呀的像个孩子,快递员压根听不到我的话,挂了,在打,又给挂了,不知道把外卖送到哪里,最终在商家的协调下开始了第二单的交易,我出不去,快递员来不了,只能重新来,终于吃上了一顿热乎乎的饺子🥟

收起思绪万千,开始代码打题怪,老生常谈,絮絮叨叨,总能在你的脑子里留下些什么。

防抖和节流

防抖功能,最早推出防抖概念的是日本尼康公司,在1994年推出了具有减震(VR)技术的袖珍相机。次年,日本佳能公司推出世界上第一支带有图像稳定器的镜头EOS 75~300mm f/4~5.6 IS,其中IS是影像稳定系统(Image Stabilizer)的缩写,这就是习惯上提到的“防抖系统”。

节流,管道中流动的流体经过通道截面突然缩小的阀门狭缝孔口等部分后发生压力降低的现象称为节流。工程上常用节流过程控制流体的压力,还可利用节流时压力降低与流量的对应关系制成流量计等。

防抖函数

将多次执行变成一次执行,主要是清空操作,将函数延时执行,如果新的操作在延时时间内,会清空之前的延时事件,重新开始一个延时事件,只执行最后一次

键盘输入模拟

设置延时时间1000,快速输入nm

测试代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>

<body>
  <label>测试节流函数</label>
  <input id='inputId' type="text" style="height: 30px;width:300px" value="" />
</body>
<script type="text/javascript">
  function debounce(f, wait) {
    let timer
    return (...args) => {
      clearTimeout(timer)
      console.log("🚀 ~ file:timer 外 ", args[0].target.value, wait)
      timer = setTimeout(() => {
        f(...args)
        console.log("🚀 ~ file:timer 内 ", args[0].target.value, wait)
      }, wait)
    }
  }
  const inputId = document.getElementById('inputId')
  inputId.addEventListener('input', debounce((event) => {
    console.log('input变化:', event.target.value)
  }, 1000))
</script>
</html>

执行效果

在单位时间1000内,输入n,开启延时事件n,再次输入m,延时事件n被重置,开启延时事件nm,到达时间后,执行输入nm

image.png

模拟执行

image.png

小结

防抖,防止抖动,重在清零,单位时间内重新触发事件重置,避免多次执行事件。

应用场景

  • 用户名密码和确认密码实时校验是否一致
  • 文本编辑器实时保存
  • 浏览器窗口resize监控等

节流

高频的事件触发,每次触发事件时都设置一个延迟调用的方法,并且取消之前的延时调用的放大,一段时间内只执行一次

滚动测试模拟

测试代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>

<body>
  <div>
    <div>
      <h2>防抖函数测试</h2>
    </div>
    <div style="height:200px;width:300px; overflow-y:auto; border: 1px solid #ccc;" id="scrollId">
      <div
        style="height:20000px; background-image:linear-gradient(to bottom right,red,orange,yellow,green,blue,indigo,violet)">
      </div>
    </div>
  </div>
</body>
<script type="text/javascript">
  // 节流测试
  function throttle(fn, wait) {
    let timer;
    return (...args) => {
      if (timer) { return }
      console.log(`setTimeout外${new Date()}`)
      timer = setTimeout(() => {
        console.log(`setTimeout内${new Date()}`)
        fn(...args)
        timer = null
      }, wait);
    }
  }
  const scrollId = document.getElementById('scrollId')
  scrollId.addEventListener('scroll', throttle((event) => {
    console.log('scroll滚动记录')
  }, 2000))
</script>

</html>

执行效果

Dec-20-2022 14-52-39.gif

模拟执行

image.png

小结

节流,控制流动速度,即控制事件的发生频率,如2S执行一次,2min执行一次

应用场景

  • 滚动事件记录,滚动一段时间切换动画
  • PDD的全面抢红包,每秒换一个动画
  • 浏览器播放事件,每隔一段时间计算一次进度

防抖函数和节流函数的区别

image.png

两个函数都是用的定时器,防抖函数的执行时间和操作频率有关,节流函数不管你操作几次固定的时间内只执行一次

防抖的执行时间和操作频率有关

代码关键在 clearTimeout

image.png

节流的执行时间和操作频率无关,每隔一段时间执行一次

代码关键在 timer = null

image.png

防抖和节流完整代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>

<body>
  <h2>测试节流函数</h2>
  <input id='inputId' type="text" style="height: 50px;width:300px;font-size: 20px;" value="" />

  <div>
    <div>
      <h2>防抖函数测试</h2>
    </div>
    <div style="height:200px;width:300px; overflow-y:auto; border: 1px solid #ccc;" id="scrollId">
      <div
        style="height:20000px; background-image:linear-gradient(to bottom right,red,orange,yellow,green,blue,indigo,violet)">
      </div>
    </div>
  </div>

</body>
<script type="text/javascript">
  // 防抖测试
  function debounce(f, wait) {
    let timer
    return (...args) => {
      clearTimeout(timer)
      console.log(`🚀 ~ file:timer 外 ${new Date()}`, args[0].target.value, wait)
      timer = setTimeout(() => {
        f(...args)
        console.log(`🚀 ~ file:timer 内${new Date()} `, args[0].target.value, wait)
      }, wait)
    }
  }

  const inputId = document.getElementById('inputId')
  inputId.addEventListener('input', debounce((event) => {
    console.log('input变化:', event.target.value)
  }, 1000))

  // 节流测试
  function throttle(fn, wait) {
    let timer;
    return (...args) => {
      if (timer) { return }
      console.log(`setTimeout外${new Date()}`)
      timer = setTimeout(() => {
        console.log(`setTimeout内${new Date()}`)
        fn(...args)
        timer = null
      }, wait);
    }
  }
  const scrollId = document.getElementById('scrollId')
  scrollId.addEventListener('scroll', throttle((event) => {
    console.log('scroll滚动记录')
  }, 2000))
</script>

</html>

闭包

其实我们刚刚在节流和防抖的时候已经悄悄使用的闭包的概念,timer在函数内部声明后,在返回的函数里又对timer进行了赋值的操作,我们把这种现象叫做闭包。

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

词法作用域

词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。嵌套函数可访问声明于它们外部作用域的变量。

 function debounce(f, wait) {
    let timer // 创建了一个局部变量
    return (...args) => {
      clearTimeout(timer)// 这个函数里没有声明timer,却可以获取timer,
                        // 因为它可以访问到外部函数的变量
      timer = setTimeout(() => {
        f(...args)
      }, wait)
    }
  }

下期预告:闭包的使用