throttle-debounce 节流和防抖

throttle节流,debounce防抖

1,区别

假设时间频率:1s

throttle 是每隔 1s,必然执行。

高铁不能人,到点就发车。

debounce 是触发后必须等待 1s 后才执行,如果等待过程中再次被触发,则重新计时。

电梯来人就感应不关门,直到没人才关闭。

基于此,常见场景:

  1. click,为了防止连续点击,应设置在点击停止一段时间后,再执行 click 事件。(debounce
    resize 同理,得文档视图调整停下后,再执行 resize 事件。(debounce
  2. scroll,会执行的很频繁,一般会将 scroll 事件隔一段时间执行。(throttle
    滚动停止后再执行的逻辑,工作场景中可能会遇到。(debounce
  3. mousemovescroll 同理(throttle
  4. change,一般不用做限制,如果是输入框,得失焦后才触发。其他的下拉框单选框什么的,也不会很频繁的调用。
  5. input,简单表单输入,到也不用做限制。
    如果是输入后要进行搜索,那肯定要加限制,等输入停止一段时间后,再执行搜索。(debounce
    即时搜索不做限制,比如浏览器的搜索行为,或者只是过滤本地数据。

2,使用

throttle-debouncenpm依赖为例。

import {
    
     throttle } from 'throttle-debounce'

function onScroll(event) {
    
    
  console.log(event)
  // ...
}

const _scroll = throttle(200, onScroll)
window.addEventListener('scroll', _scroll)
<template>
  <div>
    <button @click="debounceClick(false, $event)">debounce</button>
  </div>
</template>

<script>
import {
      
       debounce } from 'throttle-debounce'
export default {
      
      
  methods: {
      
      
    debounceClick: debounce(300, function(bool, event) {
      
      
      console.log(bool, event)
    })
  }
}
</script>

3,实现

参考1参考2,也参考了 throttle-debounce^1.0.1 | npm 源码。

3.1,debounce

基础实现

function debounce(delay, callback) {
    
    
  let timeoutId = 0
  
  function wrapper() {
    
    
    let self = this
    let args = arguments
    
    function exec() {
    
    
      callback.apply(self, args)
    }
    // 只要调用就清理,否则就耐心等待 setTimeout 执行。
    clearTimeout(timeoutId)
    timeoutId = setTimeout(exec, delay)
  }
  return wrapper
}

关于 this 的问题,因为 this 应指向返回的 wrapper 函数。

问题:即便只点一次,也得等 delay 之后才能触发。

可以加个判断,是在开始触发 或 等待 delay 后触发。

function debounce(delay, callback, atBegin) {
    
    
  let timeoutId = 0
  
  function wrapper() {
    
    
    let self = this
    let args = arguments
    
    function exec() {
    
    
      callback.apply(self, args)
    }
    
    clearTimeout(timeoutId)
    
    if (atBegin) {
    
    
      let callNow = !timeoutId
      timeoutId = setTimeout(function() {
    
    
        timeoutId = 0
      }, delay)
      if (callNow) {
    
    
        exec()
      }
    } else {
    
    
      timeoutId = setTimeout(exec, delay)
    }
  }
  return wrapper
}

或是如下写法

function debounce(delay, callback, atBegin) {
    
    
  let timeoutId = 0
  
  function wrapper() {
    
    
    let self = this
    let args = arguments
    
    function exec() {
    
    
      callback.apply(self, args)
    }
    
    function clear () {
    
    
      timeoutId = 0;
    }
    
    clearTimeout(timeoutId)
    
    if (atBegin && !timeoutId) {
    
    
      exec()
    }
    
    timeoutId = setTimeout(atBegin ? clear : exec, delay)
  }
  return wrapper
}

3.2,throttle

function throttle(delay, callback) {
    
    
  let timeoutId = 0
  let lastExec = 0
  
  // 替代后的函数
  function wrapper() {
    
    
    let args = arguments
    let self = this
    let elapsed = Number(new Date()) - lastExec // 上次执行后经过的时间
    
    function exec() {
    
    
      lastExec = Number(new Date()) // 记录执行的时间点
      callback.apply(self, args)
    }
    
    clearTimeout(timeoutId)
    if (elapsed > delay) {
    
    
      exec()
    } else {
    
    
      timeoutId = setTimeout(exec, delay - elapsed)
    }
  }
  return wrapper
}

上面的实现,有个问题就是 trailing(尾随)效果,也就是 else 是否需要触发。

比如对滚动页面来说,滚动已经停止了,但可能停止的时间点正好是 elapsed < delay,那再等 delay - elapsed 时间后,还会再触发一次。

只需要加个判断即可。

需要注意的是,npm 依赖的参数 noTrailing 意思正好相反,noTrailing = false 时,添加尾随效果。

function throttle(delay, callback, trailing = false) {
    
    
  let timeoutId = 0
  let lastExec = 0
  
  // 替代后的函数
  function wrapper() {
    
    
    let args = arguments
    let self = this
    let elapsed = Number(new Date()) - lastExec // 上次执行后经过的时间
    
    function exec() {
    
    
      lastExec = Number(new Date()) // 记录执行的时间点
      callback.apply(self, args)
    }
    
    clearTimeout(timeoutId)
    if (elapsed > delay) {
    
    
      exec()
    } else if (trailing) {
    
    
      timeoutId = setTimeout(exec, delay - elapsed)
    }
  }
  return wrapper
}

4,意外收获

函数参数顺序问题。在 throttle-debounce^1.0.1 | npm 源码。中,有如下代码:

module.exports = function ( delay, noTrailing, callback, debounceMode ) {
    
    
  // ...
  // `noTrailing` defaults to falsy.
  if ( typeof noTrailing !== 'boolean' ) {
    
    
    debounceMode = callback;
    callback = noTrailing;
    noTrailing = undefined;
  }
  // ...
}

原来当某个参数值和想要的不相符时,可以直接替换位置!


以上。

猜你喜欢

转载自blog.csdn.net/qq_40147756/article/details/132679639