二十. 防抖_ 节流函数

52 阅读3分钟

二十. 防抖_ 节流函数

防抖和节流的概念其实最早并不是出现在软件工程中,防抖是出现在电子元件中,节流出现在流体流动中

  • 而JavaScript是事件驱动的,大量的操作会触发事件,加入到事件队列中处理。
  • 而对于某些频繁的事件处理会造成性能的损耗,我们就可以通过防抖和节流来限制事件频繁的发生;

20.1. 认识防抖debounce函数

我们用一副图来理解一下它的过程:

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

image-20220330170419437-16552202735954.png

20.2. 防抖函数的案例

image-20220331112759742.png

20.3. 认识节流throttle函数

我们用一副图来理解一下节流的过程

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

image-20220330170616841.png

20.4. underscore库的介绍

事实上我们可以通过一些第三方库来实现防抖操作:

  • lodash
  • underscore

这里使用underscore

  • 我们可以理解成lodash是underscore的升级版,它更重量级,功能也更多;
  • 但是目前我看到underscore还在维护,

lodash已经很久没有更新了;

Underscore的安装有很多种方式:

  • 下载Underscore,本地引入;
  • 通过CDN直接引入;
  • 通过包管理工具(npm)管理安装;

这里我们直接通过CDN

20.5. underscore实现防抖和节流

 <input type="text">
 <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.2/underscore-umd-min.js"></script>
 ​
 <script>
     let count = 0
     const fn = function () {
         console.log(`发送了第${++count}次请求`);
     }
     // 防抖处理
     // document.querySelector("input").oninput = _.debounce(fn,2000)
     // 节流处理
     document.querySelector("input").oninput = _.throttle(fn, 2000)
 </script>

20.6. 自定义防抖函数 debounce

实现功能:正确绑定this、事件对象event、立即执行、取消功能、函数返回值

 <input type="text">
 <button id="cancel">取消</button>
 ​
 <script src="./test.js"></script>
 <script>
     let count = 0
     const fn = function (event) {
         console.log(`发送了第${++count}次请求`);
         return "123"
     }
 ​
     // 防抖处理
     const _debounce= debounce(fn, 3000, true, res=>{console.log(res);})
     document.querySelector("input").oninput = _debounce
     // 取消功能
     document.querySelector("#cancel").onclick=function(){
         _debounce.cancel()
     }
 </script>
 function debounce(fn, delay, immediate = false, resultCallback) {
     let timer = null // 延迟调用
     let isInvoke = false // 是否立即执行
 ​
     const _debounce = function (...args) {
         if (timer) clearTimeout(timer)
 ​
         // 立即执行
         if (immediate && !isInvoke) {
             const result = fn.apply(this, args)
             if (resultCallback) resultCallback(result) 
             isInvoke = true
         } else {
             // 延迟执行
             timer = setTimeout(() => {
                 const result = fn.apply(this, args)
                 if (resultCallback) resultCallback(result) 
                 isInvoke = false
             }, delay);
         }
     }
     // 取消功能
     _debounce.cancel = function () {
         if (timer) clearTimeout(timer)
         timer = null
         isInvoke = false
     }
     return _debounce
 }

20.7. 自定义节流函数throttle

实现功能:this、事件对象event、lending、trailing、取消功能、返回值

 <body>
     <input type="text">
     <button id="cancel">取消</button>
 ​
     <script src="./test.js"></script>
     <script>
 ​
         let count = 0
         const fn = function (event) {
             console.log(`发送了第${++count}次请求`, this, event);
             return 12
         }
 ​
         // 节流处理
         const _throttle = throttle(fn, 3000, {
             leading: false,
             trailing: true,
             resultCallback: function(reslut){
                 console.log(reslut);
             }
         })
         document.querySelector("input").oninput = _throttle
 ​
         // 取消功能
         document.querySelector("#cancel").onclick=_throttle.cancel
     </script>
 </body>
 function throttle(fn, interval=200, options = { leading: true, trailing: false }) {
     // 记录上一次的开始时间
     let lastTime = 0
     let timer = null
     const { leading, trailing, resultCallback } = options
 ​
     const _throttle = function (...args) {
         // 当前事件触发的时间
         const nowTime = new Date().getTime()
         if (lastTime === 0 && leading === false) lastTime = nowTime
 ​
         // nowTime距离上一次开始的时间 >= 3s时,执行函数
         const remainTime = interval - (nowTime - lastTime)
         if (remainTime <= 0) {
             // 如果正常触发了事件,则不用加定时器
             // 用户在0s输入a,触发事件,不加延时器;在1s时,输入b,加延时器,
             // 在10s时有输入触发事件时,应取消延时器
             if (timer) { clearTimeout(timer); timer = null }
             const result = fn.apply(this, args)
             if (resultCallback) resultCallback(result)
             lastTime = nowTime
             // 如果正常触发了事件,则不用加定时器
             // 用户在0s时输入了a,后续没有输入,这时会触发事件会加10s的延时器(没有必要)
             // 用户0s时输入了a,10s时又输入了b,后续没有输入,这时会触发事件会加10s的延时器(没有必要)
             return
         }
         if (trailing && !timer) {
             timer = setTimeout(() => {
                 timer = null
                 lastTime = !leading ? 0 : new Date().getTime()
                 const result = fn.apply(this, args)
                 if (resultCallback) resultCallback(result)
             }, remainTime);
         }
     }
 ​
     _throttle.cancel = function () {
         if (timer) clearTimeout(timer)
         timer = null
         lastTime = 0
     }
     return _throttle
 }