写在前面

函数防抖和函数节流都是对函数进行特殊的设置,减少该函数在某一时间段内频繁触发带来的副作用。二者只是采用的设置方式和原理不一样,其最终的目的是一样的。

函数防抖和函数节流都是定义一个函数,该函数接收一个函数作为参数,并返回一个添加了防抖或节流功能后的函数。

因此可以将函数防抖和函数节流看作是一个函数工厂,负责对传进来的函数进行相应的加工改造,然后产出一个新的带有某种功能的函数。

简单来说,防抖是会在频繁触发时,以最后一次触发为准,只执行最后一次。而节流是在多次触发时,以第一次为准,只执行第一次。

防抖适合监听输入事件和监听resize窗口大小变化时使用,这些都是只需要拿最后的结果处理就行。而节流适合点击按钮发请求这种情况,第一次发完短时间内不会再发。

假如有这样一个场景:在某一页面,有一个按钮是 加载更多,这个按钮的作用就是使用 ajax 从后端服务器请求更多的数据展示在页面,我们都知道,ajax 请求的响应是一个异步的,会存在一定的响应时间,如果用户在点击了该按钮后,立马又点击了一下该按钮,按照常规回调函数的操作,回调函数会立马又执行一次,那么就是用户短时间内点击了两次 加载更多 按钮后就会执行回调函数向后端发起了两次一模一样的 ajax 请求,服务器会一一接收请求并处理返回数据,短时间内两次请求还好,如果用户连续点击了 n 次 加载更多 按钮呢?那就会短时间内向服务器发送了 n 次一模一样的请求,每次后端处理 ajax 请求后返回数据后页面就会重新再渲染一次,尽管内容没有改变,这样就会带来性能上的问题,不仅为服务器带去了压力,也为浏览器带来了不必要的渲染,这就是函数频繁执行带来的副作用。

那么如何设置这个按钮的点击事件回调函数才能减少该函数频繁执行带来的副作用呢?

1. 函数防抖(debounce)

函数防抖的设计思路就是在函数执行前加一个等待时间,在这个等待时间内如果该函数又需要执行一次,就重新计算等待时间,再次等待,依次类推,直到等待时间到了,还没有等到函数又需要执行的情况,才会执行这个函数。

就拿上述的场景来说,假设向后台发送 ajax 请求的响应时间大概是在 2s 左右,那就设置这个等待时间为 2s,当用户第一次点击 加载更多 按钮后,该回调函数并没有立即执行,也就是还没有开始发送 ajax,此时该函数在等待,如果在 2s 内用户又点击了 加载更多,那就重新计算等待时间,再等待 2s ,此时 2s 过去了,用户没再第三次点击 加载更多 按钮了,那么该函数就开始执行了,向后端发送 ajax 请求了。

函数防抖实现方式如下:

  1. function debounce(fn, delay){
  2. let timeId = null
  3. return function(){
  4. let context = this
  5. if(timeId){window.clearTimeout(timeId)}
  6. timeId = setTimeout(()=>{
  7. fn.apply(context, arguments)
  8. timeId = null
  9. },delay)
  10. }
  11. }

2. 函数节流(throttle)

函数节流的设计思路是在函数执行后加一个冷却时间,函数在第一次执行时是立马执行,但在其执行完后设置一个冷却时间,在冷却时间内,该函数不能再次执行,直到冷却时间结束允许该函数执行了,才可以再次执行。

就拿上述的场景来说,冷却时间假设也设置为 2s,在用户第一次点击 加载更多 后按钮的回调函数就会执行,也就是会向后台发送 ajax 请求,此时用户又立马再次点击了 加载更多 按钮,由于此刻 2s 的冷却时间还没有到,那么就会给第二次的函数执行就会被废弃。

  1. function throttle(fn, delay){
  2. let canUse = true
  3. return function(){
  4. if(canUse){
  5. fn.apply(this, arguments)
  6. canUse = false
  7. setTimeout(()=>canUse=true, delay)
  8. }
  9. }
  10. }