likes
comments
collection

用小例子讲防抖节流

作者站长头像
站长
· 阅读数 8

防抖与节流可以说是我们开发中使用频率非常高的了,也是我们面试常用的点之一。所以理解好防抖与节流就显得十分必要!

其实防抖与节流的场景就是我们用户交互过快,而这些交互就会触发很多事件,比如轮播图点太快就会切换得很快,分页点击过快就会发很多请求等等。而防抖与节流就是为了在这种过快的场景中,优化性能或者防止事件触发过多等等。

防抖

防抖的作用就是设置一个时间,在这个时间段中如果再次触发交互就会清除上一个交互。

比如我们登陆的时候,用户点击过快,1s就点击了很多下,这时候就会向后台发送很多次请求,如果我们设置200ms内如果再次触发了这个交互,那么上次交互就无效了,所以这里就会让我们的登录只有最后一次才有效。也就不会引起发很多次请求了。

如果用现实中的例子来说的话,就是坐电梯:当电梯要关闭时,如果又有人进来,那么电梯就会停止关闭,一直等到电梯要关完了都没有人进来,电梯才会真正的关闭。

用代码实现的话,就是:

import {login} from './server/api'
let timer = undefined;
const login =  () => {
    clearTimeout(timer);
    timer = setTimeout(async ()=>{
        let res = await login();
        ...
    },200)
}

这个的意思就是如果我们点击登录后,会在200ms后才向后台发送请求,如果在200ms之内,我们再触发这个登录事件,就会清除上一次的定时器,重新开始计时。

除此之外,用到防抖的地方还有很多很多,比如:搜索,当我们绑定input事件后,value每改变一次就会触发这个事件向后台请求内容,而用户此时需要搜索的内容还没写完,可我们就已经发送了很多次请求了,正确的做法是利用防抖,当用户最后一次输入后一段时间内不再输入就判定输入完成,再向后台发送请求。

再比如分页、文本编辑器实时保存(当无任何更改操作一秒后进行保存)、DOM 元素的拖拽功能实现、计算鼠标移动的距离等等。

封装防抖

利用闭包封装防抖。

  • fn代表要执行的函数(交互后要触发的事件)
  • wait代表等待时间
  • immediate代表触发后是否立即执行(第一次)
function debounce(fn, wait, immediate) {
  let timeout, result;
  return function() { 
    let args = arguments   // 接受event等默认传递的参数
    clearTimeout(timeout)
    if (immediate) {  //如果第一次需要立即执行
      let callNow = !timeout
      timeout = setTimeout(() => { //立即执行后当n秒内触发事件才能再次执行。
        timeout = null
      }, wait)
      if (callNow) result = fn.apply(this, args)
    } else {
      timeout = setTimeout(() => { //箭头函数解决this指向正确
        fn.apply(this, args)
      }, wait)
    }
    return result
  }
}

节流

而节流看其字面意思就是“节制流入流出、开源节流”的意思。所以它的功能就是防止不必要的流程。

节流在我们开发中使用的场景也是很多的,在开发中的意思就是:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。比如我们切换轮播图,哪怕我们按十下下一张,只要下一张还没完全切换完毕,就只会执行一次。

let flag = false;
nextSlide.addEventListener('click',function(){
    if(!flag) {
        flag = true;
        next(()=>{flag=flase})
    } 
})

function next(callback) {
    ....
    if(callback) {
        callback()
    }
    ...
}

这个意思就是只有将flag作为开关,当开关打开时,才会执行,执行时就会关闭开关,当执行完毕后,又会打开开关。

此外,我们也可以使用定时器。

function throttle(fn,wait) {
    let timer;
    return function() {
        let args = arguments;
        if(!timer) {
            timer = setTimeout(()=>{
                timer = null;
                fn.apply(this,args);
            },wait)
        }
    }
}

原理和上面是一样的,只是上面利用回调函数,这里利用闭包。这里的timer也可以改成flag都可以。

但是这有个缺点就是没有立即执行,所以我们可以利用时间戳来完成一个可以立即执行的节流函数:

function throttle(fn, wait) {
    let prev  = 0
    return function () {
        let now = Date.now();
        if (now - prev > wait) {
            fn();
            prev  = now;
        }
    }
}

节流和防抖在我们的开发中真的很常见很常见,所以还是需要掌握好这两种实现与区别。

总结

函数节流与函数防抖都是为了限制函数的执行频次,都是一种性能优化的方法。区别是防抖是有条件的周期性动作,而节流是无条件的周期性动作。两者实现的核心都依赖于闭包。