防抖、节流

177 阅读3分钟

输入框监听例子

这是一个常见的事件监听功能,很多网站的搜索框需要通过输入文本框的内容实时检索数据

每输入一个字符就会触发一次函数执行,因此函数的执行频率很高;很可能内容还没输入完就已经触发函数执行。

然而实际上我们并不需要这么高频率的执行反馈,而且浪费浏览器性能,甚至可能牵涉后台请求而影响服务器性能;所以这也是性能优化的场景。

防抖(debounce)

针对上述场景,提出第一种解决思路:防抖

什么是防抖?

指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

效果:短时间内多次触发同个事件,只会执行一次函数。

实现:setTimeOut函数

// fn是事件触发执行的函数
// wait是指等待时长,在wait时间内多次触发函数只会被执行一次
// immediate是否立即执行,true表示wait秒内触发只会执行第一次触发;false表示wait秒内触发只会执行最后一次触发
function debounce(fn, wait = 1000, immediate = false) {
  var timeout;
  return function() {
    let ctx = this;
    let args = arguments;

    if(timeout) clearTimeout(timeout);
    if(immediate) {
      let callNow = !timeout;
      // wait秒后清除定时器
      timeout = setTimeout(()=>{timeout = null;}, wait);
      // 先执行函数
      if(callNow) fn.apply(ctx, args)
    }else {
      // wait秒后执行fn
      timeout = setTimeout(()=>{fn.apply(ctx, args)}, wait);
    }
  }
}

至此,输入框防抖实现了,采用非立即执行方式,确保获取到最新的输入框值,n秒内没有再次输入才会触发函数,否则重新倒计时n秒,直到n秒内没有再次输入的时候才会触发函数执行。

节流(throttle)

上面输入框例子中防抖的实现是n秒内不再输入则触发函数执行,然而加入我们一直保持n秒内输入,则将永远无法触发函数,或者实际场景下可能很长一段时间无法触发。

如果希望即使用户不停输入,也能够在某个时间间隔反馈(触发函数),因此有了第二种解决思路:节流

什么是节流?

指连续触发事件但是在 n 秒中只执行一次函数,所以节流稀释了函数执行的频率

效果:n秒内多次触发同个事件,只会执行一次函数,直到下一个n秒才会重新生效。

实现:setTimeOut函数

// fn是事件触发执行的函数
// wait是指间隔时长,在wait时间内多次触发函数只会被执行一次
// timeStamp: true时间戳版,false定时器版
function throttle(fn, wait = 1000, timeStamp = false) {
  let previous = 0;
  let timeout;
  return function() {
    let ctx = this;
    let args = arguments;
    if(timeStamp){
      let now = Date.now();
      if(now - previous > wait) {
        fn.apply(ctx, args);
        previous = now;
      }
    }else {
      if(!timeout) {
        timeout = setTimeout(()=>{
          timeout = null;
          fn.apply(ctx, args)
        }, wait)
      }
    }
  }
}

其它常见应用场景

  1. 滚动监听,返回顶部功能,即监听浏览器滚动事件,返回当前滚动条位置与顶部距离。可以采用节流方式,当滚动条滚动时以n秒的时间间隔获取当前位置与浏览器顶部距离。

  2. 页面resize事件

  3. 按钮提交事件

  4. 拖拽事件

  5. ...

总结

防抖和节流都是为了解决频繁触发某个事件造成的性能消耗,在用户体验允许的情况下减少性能浪费。

在项目中不乏应用场景,至于选用防抖还是节流来降低性能损耗,可根据项目实际需求而定。