• JavaScript 防抖和节流


    一、防抖函数( debounce )

    简单的理解就是:延迟执行

    1.用一副图来理解一下它的过程:

    ①当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间;
    ②当事件密集触发时,函数的触发会被频繁的推迟;
    ③只有等待了一段时间也没有事件触发,才会真正的执行响应函数;

    2.防抖的应用场景:

    ①输入框中频繁的输入内容,搜索或者提交信息;
    ②频繁的点击按钮,触发某个事件;
    ③监听浏览器滚动事件,完成某些特定操作;
    ④用户缩放浏览器的resize事件;

    3.第三方库实现防抖

    目前防抖节流的第三方库有 lodash 、underscore
    这里使用underscore,我们可以理解成lodash是underscore的升级版,它更重量级,功能也更多;但是目前underscore还在维护,lodash已经很久没有更新了;
    Underscore的安装有很多种方式:
    ① 下载Underscore,本地引入;
    ② 通过CDN直接引入;
    ③ 通过包管理工具(npm)管理安装;

    4.简单案例:

    输入框中输入内容实现防抖,代码如下

    <script>
       //需要先安装和引入Underscore
        1.获取输入框
        const inputEl = document.querySelector("input")
        let counter = 0
        2.触发的事件
        const inputChange = function(event) {
          console.log(`发送了第${++counter}次网络请求`, this, event)
        }
        // 防抖处理,停止输入2s后触发事件
        inputEl.oninput = _.debounce(inputChange, 2000)
    </script>
    

    5.手写防抖函数

    ①基本实现

    function debounce(fn, delay) {
      // 1.定义一个定时器, 保存上一次的定时器
      let timer = null
    
      // 2.真正执行的函数
      const _debounce = function() {
        // 取消上一次的定时器
        if (timer) clearTimeout(timer)
        // 延迟执行
        timer = setTimeout(() => {
          // 外部传入的真正要执行的函数
          fn()
        }, delay)
      }
    
      return _debounce
    }
    

    ②事件函数有参数,需要改变this指向

    function debounce(fn, delay) {
      // 1.定义一个定时器, 保存上一次的定时器
      let timer = null
    
      // 2.真正执行的函数
      const _debounce = function(...args) {
        // 取消上一次的定时器
        if (timer) clearTimeout(timer)
        // 延迟执行
        timer = setTimeout(() => {
          // 外部传入的真正要执行的函数
          fn.apply(this, args)
        }, delay)
      }
    
      return _debounce
    }
    

    ③增加立即执行的功能,第一次输入先触发一次事件

    function debounce(fn, delay, immediate = false) {
      // 1.定义一个定时器, 保存上一次的定时器
      let timer = null
      let isInvoke = false //是否执行过
    
      // 2.真正执行的函数
      const _debounce = function(...args) {
        // 取消上一次的定时器
        if (timer) clearTimeout(timer)
    
        // 判断是否需要立即执行
        if (immediate && !isInvoke) {
          fn.apply(this, args)
          isInvoke = true
        } else {
          // 延迟执行
          timer = setTimeout(() => {
            // 外部传入的真正要执行的函数
            fn.apply(this, args)
            isInvoke = false
          }, delay)
        }
      }
    
      return _debounce
    }
    

    ④增加取消功能,取消要执行的事件

    function debounce(fn, delay, immediate = false) {
      // 1.定义一个定时器, 保存上一次的定时器
      let timer = null
      let isInvoke = false
    
      // 2.真正执行的函数
      const _debounce = function(...args) {
        // 取消上一次的定时器
        if (timer) clearTimeout(timer)
    
        // 判断是否需要立即执行
        if (immediate && !isInvoke) {
          fn.apply(this, args)
          isInvoke = true
        } else {
          // 延迟执行
          timer = setTimeout(() => {
            // 外部传入的真正要执行的函数
            fn.apply(this, args)
            isInvoke = false
            timer = null
          }, delay)
        }
      }
    
      // 封装取消功能
      _debounce.cancel = function() {
        if (timer) clearTimeout(timer)
        timer = null
        isInvoke = false
      }
    
      return _debounce
    }
    
    

    调用取消功能:

     const cancelBtn = document.querySelector("#cancel")
     cancelBtn.onclick = function() {
          debounceChange.cancel()
        }
    

    ⑤事件有返回值

    function debounce(fn, delay, immediate = false, resultCallback) {
      // 1.定义一个定时器, 保存上一次的定时器
      let timer = null
      let isInvoke = false
    
      // 2.真正执行的函数
      const _debounce = function(...args) {
        return new Promise((resolve, reject) => {
          // 取消上一次的定时器
          if (timer) clearTimeout(timer)
    
          // 判断是否需要立即执行
          if (immediate && !isInvoke) {
            const result = fn.apply(this, args)
            if (resultCallback) resultCallback(result)
            resolve(result)
            isInvoke = true
          } else {
            // 延迟执行
            timer = setTimeout(() => {
              // 外部传入的真正要执行的函数
              const result = fn.apply(this, args)
              if (resultCallback) resultCallback(result)
              resolve(result)
              isInvoke = false
              timer = null
            }, delay)
          }
        })
      }
    
      // 封装取消功能
      _debounce.cancel = function() {
        if (timer) clearTimeout(timer)
        timer = null
        isInvoke = false
      }
    
      return _debounce
    }
    
    

    调用有返回值:

        const debounceChange = debounce(inputChange, 3000, false, (res) => {
          console.log("拿到真正执行函数的返回值:", res)
        })
        const tempCallback = () => {
          debounceChange().then(res => {
            console.log("Promise的返回值结果:", res)
          })
        }
      inputEl.oninput = tempCallback
    

    二、节流函数( throttle )

    简单的理解就是减少事件的触发频率

    1.用一副图来理解一下它的过程:

    ①当事件触发时,会执行这个事件的响应函数;
    ②如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数;
    ③不管在这个中间有多少次触发这个事件,执行函数的频繁总是固定的;

    2.节流的应用场景:

    ① 监听页面的滚动事件;
    ② 鼠标移动事件;
    ③ 用户频繁点击按钮操作;
    ④ 游戏中的一些设计;

    3.第三方库实现防抖

    目前防抖节流的第三方库有 lodash 、underscore

    4.简单案例:

    点击按钮不断触发事件,实现节流,代码如下

    <script>
        //首先需要安装和导入第三方库
        const inputEl = document.querySelector("input")
        let counter = 0
        const inputChange = function(event) {
          console.log(`发送了第${++counter}次网络请求`, this, event)
        }
       inputEl.oninput = _.throttle(inputChange, 2000) //2s内只能触发一次事件
    </script> 
    

    5.手写节流函数

    ①基本实现

    function throttle(fn, interval, options) {
      // 1.记录上一次的开始时间
      let lastTime = 0
    
      // 2.事件触发时, 真正执行的函数
      const _throttle = function() {
    
        // 2.1.获取当前事件触发时的时间
        const nowTime = new Date().getTime()
    
        // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
        const remainTime = interval - (nowTime - lastTime)
        if (remainTime <= 0) {
          // 2.3.真正触发函数
          fn()
          // 2.4.保留上次触发的时间
          lastTime = nowTime
        }
      }
    
      return _throttle
    }
    

    ②增加开头和结尾执行是否执行事件

    function throttle(fn, interval, options = { leading: true, trailing: false }) {
      // 1.记录上一次的开始时间
      const { leading, trailing } = options
      let lastTime = 0
      let timer = null
    
      // 2.事件触发时, 真正执行的函数
      const _throttle = function() {
    
        // 2.1.获取当前事件触发时的时间
        const nowTime = new Date().getTime()
        if (!lastTime && !leading) lastTime = nowTime
    
        // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
        const remainTime = interval - (nowTime - lastTime)
        if (remainTime <= 0) {
          if (timer) {
            clearTimeout(timer)
            timer = null
          }
    
          // 2.3.真正触发函数
          fn()
          // 2.4.保留上次触发的时间
          lastTime = nowTime
          return
        }
    
        if (trailing && !timer) {
          timer = setTimeout(() => {
            timer = null
            lastTime = !leading ? 0: new Date().getTime()
            fn()
          }, remainTime)
        }
      }
    
      return _throttle
    }
    
    

    ③this指向和参数

    function throttle(fn, interval, options = { leading: true, trailing: false }) {
      // 1.记录上一次的开始时间
      const { leading, trailing } = options
      let lastTime = 0
      let timer = null
    
      // 2.事件触发时, 真正执行的函数
      const _throttle = function(...args) {
    
        // 2.1.获取当前事件触发时的时间
        const nowTime = new Date().getTime()
        if (!lastTime && !leading) lastTime = nowTime
    
        // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
        const remainTime = interval - (nowTime - lastTime)
        if (remainTime <= 0) {
          if (timer) {
            clearTimeout(timer)
            timer = null
          }
    
          // 2.3.真正触发函数
          fn.apply(this, args)
          // 2.4.保留上次触发的时间
          lastTime = nowTime
          return
        }
    
        if (trailing && !timer) {
          timer = setTimeout(() => {
            timer = null
            lastTime = !leading ? 0: new Date().getTime()
            fn.apply(this, args)
          }, remainTime)
        }
      }
    
      return _throttle
    }
    
    

    ④增加取消功能,取消要执行的事件

    function throttle(fn, interval, options = { leading: true, trailing: false }) {
      // 1.记录上一次的开始时间
      const { leading, trailing } = options
      let lastTime = 0
      let timer = null
    
      // 2.事件触发时, 真正执行的函数
      const _throttle = function(...args) {
    
        // 2.1.获取当前事件触发时的时间
        const nowTime = new Date().getTime()
        if (!lastTime && !leading) lastTime = nowTime
    
        // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
        const remainTime = interval - (nowTime - lastTime)
        if (remainTime <= 0) {
          if (timer) {
            clearTimeout(timer)
            timer = null
          }
    
          // 2.3.真正触发函数
          fn.apply(this, args)
          // 2.4.保留上次触发的时间
          lastTime = nowTime
          return
        }
    
        if (trailing && !timer) {
          timer = setTimeout(() => {
            timer = null
            lastTime = !leading ? 0: new Date().getTime()
            fn.apply(this, args)
          }, remainTime)
        }
      }
    
      _throttle.cancel = function() {
        if(timer) clearTimeout(timer)
        timer = null
        lastTime = 0
      }
    
      return _throttle
    }
    
    

    ⑤事件有返回值

    function throttle(fn, interval, options = { leading: true, trailing: false }) {
      // 1.记录上一次的开始时间
      const { leading, trailing, resultCallback } = options
      let lastTime = 0
      let timer = null
    
      // 2.事件触发时, 真正执行的函数
      const _throttle = function(...args) {
        return new Promise((resolve, reject) => {
          // 2.1.获取当前事件触发时的时间
          const nowTime = new Date().getTime()
          if (!lastTime && !leading) lastTime = nowTime
    
          // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
          const remainTime = interval - (nowTime - lastTime)
          if (remainTime <= 0) {
            if (timer) {
              clearTimeout(timer)
              timer = null
            }
    
            // 2.3.真正触发函数
            const result = fn.apply(this, args)
            if (resultCallback) resultCallback(result)
            resolve(result)
            // 2.4.保留上次触发的时间
            lastTime = nowTime
            return
          }
    
          if (trailing && !timer) {
            timer = setTimeout(() => {
              timer = null
              lastTime = !leading ? 0: new Date().getTime()
              const result = fn.apply(this, args)
              if (resultCallback) resultCallback(result)
              resolve(result)
            }, remainTime)
          }
        })
      }
    
      _throttle.cancel = function() {
        if(timer) clearTimeout(timer)
        timer = null
        lastTime = 0
      }
    
      return _throttle
    }
    
    

    调用:

        const _throttle = throttle(inputChange, 3000, { 
          leading: false, 
          trailing: true,
          resultCallback: function(res) {
            console.log("resultCallback:", res)
          }
         })
        const tempCallback = (...args) => {
          _throttle.apply(inputEl, args).then(res => {
            console.log("Promise:", res)
          })
        }
        inputEl.oninput = tempCallback
    
  • 相关阅读:
    Java List集合
    Java 集合删除重复元素、删除指定元素
    进程和线程
    Java 线程的同步与死锁
    Java 线程的常用操作方法
    Java 多线程实现
    统计分析
    递归方法(回文)
    素数的输出
    动手动脑二
  • 原文地址:https://www.cnblogs.com/qingheshiguang/p/16007319.html
Copyright © 2020-2023  润新知