关于防抖、节流和倒计时我所知道的

124 阅读4分钟

关键词:定时器 性能优化 防抖节流 倒计时

防抖节流解决什么问题?

本质其实就是控制函数在一段时间内被执行的次数。

防抖节流
区别最后只会调用最后一次每隔一定时间调用一次函数
相同防止函数多次调用防止函数多次调用
应用场景input/clickresize/touchmove/mousemove/scroll

防抖是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。

比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车。

开发中最常用的就是防抖,表单输入、提交按钮等。

节流的概念是指一定时间内函数只运行一次。 throttle 会强制函数以固定的速率执行。因此这个方法比较适合应用于动画相关的场景。

比如我们排队做核酸,一个窗口一分钟只能做一个核酸。

开发中最常见的例子就是用户向下滚动无限加载的页面/表格,需要定时检查用户离底部的距离,然后判断是否需要请求数据append 进列表。这种情况防抖就不适合了,防抖是在用户停止滚动时触发,而我们需要在用户到达底部之前酒开始获取数据。

函数防抖

  1. 函数在一段时间内多次触发只会执行第一次,在这段时间结束前,不管触发多少次也不会执行函数。
  2. 在等待时间内触发此函数,则重新计算等待时间。

函数节流

  1. 在操作结束后才执行
  2. 如果超过了设定的时间,就执行一次处理函数。

react hooks 如何实现?

Vue 实现防抖

不要直接在组件的 method 选项中创建防抖函数,然后在 template 中调用这些方法作为事件处理器。

原因是组件使用 export default { ... } 导出的 options 对象,包括方法,会被组件实例重用。如果网页中有 2 个以上的组件实例,那么所有的组件都会应用相同的防抖函数 methods.debouncedHandler 会导致防抖出现故障。

倒计时

setInterval:如果 代码 执行时间比设定的时间间隔还要常,建议递归调用 setTimeout。因为 JS 是单线程语言,如果前面有阻塞线程的任务,例如:网络延迟或者服务器无响应等问题。会导致 setInterval 延迟,这样倒计时就肯定会不准确。

setTimeout:有很多因素会导致 setTimeout 的回调函数执行比设定的预期值更久,比如嵌套超时、非活动标签超时、追踪型脚本的节流、超时延迟。

// setInterval 
function countdown(endTime, cb) {
  let t = endTime;
  const timer = setInterval(() => {
    t = t - 1000;
    if(t <= 0 ) {
      clearInterval(timer)
      return
    }
    cb(t)
  }, 1000);
}

countdown(10000, (t) => console.log(t))

// setTimeout
function countdown(endTime, cb) {
  let t = endTime;
  const timer = setTimeout(() => {
    t -= 1000;
    if (t > 0) {
      cb(t)
      countdown(t, cb);
      return false
    }
    clearTimeout(timer);
  }, 1000)
}

countdown(10000, (t) => console.log(t))

利用 requestAnimationFrame 结合 setTimeout 来优化。

requestAnimationFrame,浏览器API,允许以 60 帧/秒 (FPS) 的速率请求回调,而不会阻塞主线程。

performance.now():返回值表示为从time origin之后到当前调用时经过的时间。

function countdown(endTime) {
  const now = performance.now();
  function start() {
    const timer = setTimeout(() => {
      const diff = endTime - (performance.now() - now);
      console.log(diff)
      if (diff <= 0) {
        clearTimeout(timer);
      } else {
        requestAnimationFrame(start);
      }
    }, 1000)
  }
  start()
}

countdown(10000)

最后综合起来写一个通用的倒计时方法。

class CountDown {
  constructor({ leftTime, ms = 1000, onEnd }) {
    this.leftTime = leftTime;
    this.ms = ms;
    this.onEnd = onEnd;
    this.countdownTimer = null;
    this.startTime = performance.now();
    this.nextTime = leftTime % ms;
    this.totalTime = 0;
    this.count = leftTime;

    this.startCountDown();
  }

  clearTimer() {
    if (this.countdownTimer) {
      clearTimeout(this.countdownTimer);
      this.countdownTimer = null;
    }
  }

  startCountDown(nt = 0) {
    this.clearTimer();

    const executionTime = performance.now() - this.startTime;
    this.totalTime += executionTime;

    this.updateCount(executionTime, nt);

    this.nextTime = this.ms - (this.totalTime % this.ms);
    this.startTime = performance.now();

    if (this.count <= 0) {
      return false;
    }

    this.countdownTimer = setTimeout(() => {
      requestAnimationFrame(() => this.startCountDown(0));
    }, this.nextTime);
  }

  updateCount(executionTime, nt) {
    this.count -= (Math.floor(executionTime / this.ms) || 1) * this.ms + nt;
    if (this.count <= 0) {
      this.count = 0;
      this.clearTimer();
      if (this.onEnd) {
        this.onEnd();
      }
    }
  }
}

// 使用示例
const countdown = new CountDown({
  leftTime: 20000,
  ms: 1000,
  onEnd: () => console.log("倒计时结束"),
});

参考资料:

一起围观由React Hooks防抖引发的面试翻车现场

函数防抖与函数节流

Debouncing and Throttling Explained Through Examples 这篇文章写得非常好,例子举得也好。