【Day04】介绍防抖节流原理、区别以及应用,并用 JavaScript 进行实现

防抖函数

一、实现原理

  • 在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。

二、适用场景

  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次

  • 搜索框联想场景:防止联想发送请求,只发送最后一次输入

三、代码实现

  • 基础代码,如下所示:
      <button onclick="func()">按钮button>
    

简易版 (非立即执行版) 实现

  • 封装函数,代码如下:
      /**
       * @param {Function} func 需要防抖处理的函数
       * @param {Number} wait 时间间隔
       * @return {Function} 将 debounce 处理结果当作函数返回
       */
      function debounce(func, wait) {
           
          // 通过闭包缓存一个定时器 id
          let timeout;
          // 将 debounce 处理结果当作函数返回
          // 触发事件回调时执行这个返回函数
          return function () {
           
              const context = this
              const args = arguments
              // 如果已经设定过定时器就清空上一次的定时器
              if (timeout) clearTimeout(timeout)
              // 开始设定一个新的定时器,定时器结束后执行回调函数 func
              timeout = setTimeout(function () {
           
                  func.apply(context, args)
              }, wait)
          }
      }
    
  • 测试示例,代码如下:
      // 执行 debounce 函数返回新函数
      const func = debounce(() => {
           
          console.log("func 防抖函数执行了" + Date.now())
      }, 1000)
    
      // 停止滑动 1 秒后执行函数 
      document.addEventListener('scroll', func)
    

立即执行版实现

  • 有时希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。
     /** 
      * @param {Boolean} immediate 表示第一次是否立即执行
     */
     function debounce(func, wait, immediate) {
           
         let timeout;
         return function () {
           
             const context = this;
             const args = arguments;
             if (timeout) clearTimeout(timeout);
             // ------ 新增部分 start ------ 
             // immediate 为 true 表示第一次触发后执行
             if (immediate) {
           
                 const callNow = !timeout;
                 timeout = setTimeout(function () {
           
                     timeout = null;
                 }, wait)
                 // timeout 为空表示首次触发
                 if (callNow) func.apply(context, args)
                 // ------ 新增部分 end ------ 
             } else {
           
                 timeout = setTimeout(function () {
           
                     func.apply(context, args)
                 }, wait);
             }
         }
     }
    
  • 测试示例,代码如下:
      // 执行 debounce 函数返回新函数
     const func = debounce(() => {
           
          console.log("func 防抖函数执行了" + Date.now())
      }, 1000, true)
      
      // 第一次触发 scroll 执行一次 func,后续只有在停止滑动 1 秒后才执行函数 func
      document.addEventListener('scroll', func)
    

返回值版实现

  • func 函数可能会有返回值,所以需要返回函数结果,但是当 immediatefalse 的时候,因为使用了 setTimeout,我们将 func.apply(context, args) 的返回值赋给变量,最后再 return 的时候,值将会一直是 undefined,所以只在 immediatetrue 的时候返回函数的执行结果。

      function debounce(func, wait, immediate) {
           
         let timeout, result;
         return function () {
           
             const context = this;
             const args = arguments;
             if (timeout) clearTimeout(timeout);
             if (immediate) {
           
                 const callNow = !timeout;
                 timeout = setTimeout(function () {
           
                     timeout = null;
                 }, wait)
                 // ------ 修改部分 start ------ 
                 if (callNow) result = func.apply(context, args)
                 // ------ 修改部分 end ------ 
    
             } else {
           
                 timeout = setTimeout(function () {
           
                     func.apply(context, args)
                 }, wait);
             }
             // 如果 immediate 为 false 的话,会先进行 return result,此时 result 为 undefined
             // 再去 执行 setTimeout() 中的操作
             return result;
         }
     }
    
  • 测试示例,代码如下:

     // 执行 debounce 函数返回新函数
     const func = debounce(() => {
           
          return 1;
      }, 1000, true)
      
      // 第一次触发 scroll 执行一次 func,后续只有在停止滑动 1 秒后才执行函数 func
      document.addEventListener('scroll', func)
    

节流函数

一、实现原理

  • 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

二、适用场景

  • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动

  • 缩放场景:监控浏览器 resize

三、代码实现

使用时间戳实现

  • 使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。

      /** 
       * @param {Function} func 需要执行的函数
       * @param {Number} wait 时间间隔
       * @return {Function} 将 throttle 处理结果当作函数返回
      */
      function throttle(func, wait) {
           
          // 上一次执行 fn 的时间
          let previous = 0;
    
          return function () {
           
              // 获取当前时间,转换成时间戳,单位毫秒
              let now = +new Date();
              const context = this;
              const args = arguments;
              // 将当前时间和上一次执行函数的时间进行对比   
              if (now - previous > wait) {
           
                  // 大于等待时间就把 previous 设置为当前时间并执行函数 func 
                  func.apply(context, args);
                  previous = now;
              }
          }
      }
    

使用定时器实现

  • 当触发事件的时候,我们设置一个定时器,在触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。

      function throttle(func, wait) {
           
          let timeout;
          return function () {
           
              const context = this;
              const args = arguments;
              // 如果定时器为空,则为首次触发
              if (!timeout) {
           
                  // 开启定时器
                  timeout = setTimeout(function () {
           
                      // 清空定时器
                      timeout = null;
                      func.apply(context, args)
                  }, wait)
              }
    
          }
      }
    

加强版 throttle

  • 结合 throttledebounce 代码,新增逻辑在于当前触发时间和上次触发的时间差小于时间间隔时,设立一个新的定时器,相当于把 debounce 代码放在了小于时间间隔部分。

      function throttle(fn, wait) {
           
          // previous 是上一次执行 fn 的时间
          // timer 是定时器
          let previous = 0, timer = null
          // 将 throttle 处理结果当作函数返回
          return function (...args) {
           
              // 获取当前时间,转换成时间戳,单位毫秒
              let now = +new Date()
              // ------ 新增部分 start ------ 
              // 判断上次触发的时间和本次触发的时间差是否小于时间间隔
              if (now - previous < wait) {
           
                  // 如果小于,则为本次触发操作设立一个新的定时器
                  // 定时器时间结束后执行函数 fn 
                  if (timer) clearTimeout(timer)
                  timer = setTimeout(() => {
           
                      previous = now
                      fn.apply(this, args)
                  }, wait)
                  // ------ 新增部分 end ------ 
              } else {
           
                  // 第一次执行
                  // 或者时间间隔超出了设定的时间间隔,执行函数 fn
                  previous = now
                  fn.apply(this, args)
              }
          }
      }
    
  • 测试示例,代码如下:

     // 执行 throttle 函数返回新函数
      const func = throttle(function() {
            
      	console.log('func 节流执行了')
      }, 1000)
      // 第一次触发 scroll 执行一次 func,每隔 1 秒后执行一次函数 func,停止滑动 1 秒后再执行函数 func
      document.addEventListener('scroll', func)
    

你可能感兴趣的:(每日一题,javascript)