JavaScript 防抖 节流

202 阅读4分钟

我做过一个项目,里面有这么一个需求,当用户输入提现金额的时候,需要调用接口计算出对应的手续费。 怎么知道用户输完并确定好提现金额了呢?假设用户心里确定要提现的金额是2000,但是他一开始只输入了200,停顿了一会,又输入了个0,手抖了一下多输入了个0,现在输入框中是20000,这个用户定睛一看,呀多输入了个0,赶紧又删了个0,这会终于输入心中所想:2000。

怎么办?难道用户每输入一个数字都调用接口?

我的内心慌了,后端小伙伴会把我吊打的

防抖隆重登场!!

防抖(debounce)

事件被频繁触发的情况下,一定时间间隔内没有再次触发,该事件函数会执行一次,如果在时间间隔结束之前事件被再次触发,则重新延时执行。

心情复杂的我看到这概念性知识,依旧心情复杂。既然心情还是复杂,那不如看个例子复杂多一会~

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Input</title>
  </head>
  <body>
    <input type="text" id="input" />
  </body>
  <script>
    const myInput = document.querySelector('input');
    myInput.oninput = debounce(count, 1000); // 用户输入时触发防抖函数

    /* 防抖函数 */
    function debounce(fn, wait, ...args) {
      let timer = null;
      return function() {
        const context = this;
        // 当用户输入内容的时候,如果存在定时器,清除定时器。保证用户输入之后,如果wait间隔内仍然有输入,则不执行fn函数
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
          fn.apply(context, args);
        }, wait);
      };
    }

    /* 处理一些需要防抖的工作 */
    function count() {
      console.log('获取手续费');
    }
  </script>
</html>

看看效果(๑•̀ㅂ•́)و✧

结合代码和效果图,我们可以看到,当用户不间断地输入内容,会不断清除上一个定时器并且生成新的定时器,等用户内容输入结束之后,间隔1000ms之后,控制台才会输出“获取手续费”。

这样就不会在用户每输入一个数字之后都调用一次接口。瞬间不慌了~

在另一个项目中,我需要实现一个比较常见的功能,页面滚动到底部的时候自动加载下一页数据。二话不说上手就给页面添加了滚动事件,接着眼拙!手抖!二愣子!似的把接口请求往里一放....页面一刷....一滚....突然背脊一阵凉风,后台老大哥:“你是在DDoS吗?!”

其实我内心想说:我只是想测一下滚动条事件是否监听成功了而已╥﹏╥...

还是回来好好地写一个判断页面是否滚动到底部的逻辑吧。

window.addEventListener('scroll', onScroll); // 监听滚动条事件
function onScroll() {
  //真实内容的高度
  const pageHeight = Math.max(document.body.scrollHeight, document.body.offsetHeight);
  //视窗的高度
  const viewportHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0;
  //隐藏的高度
  const scrollHeight = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
  console.log('scroll');
  if (pageHeight - scrollHeight - viewportHeight <= 0) {
    console.log('底部');
  }
}

当滚动到了底部,就开始下一页数据的请求。看似解决了这个问题,其实这样做是很消耗性能的,当用户在滚动的时候,浏览器会无时不刻地计算和判断页面是否已经滚动到底部。我们看代码运行之后的效果图:

这个时候我们就要把节流隆重推出来啦!!

节流(throttle)

一定时间间隔内持续触发事件,只会执行一次处理函数。

添加节流之后的代码:

window.addEventListener('scroll', throttle(onScroll, 1000)); // 监听滚动条事件

/* 节流函数 */
function throttle(fn, wait, ...args) {
  let timer = null;
  return function() {
    const content = this;
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(content, args);
        timer = null;
      }, wait);
    }
  };
}

function onScroll() {
  //真实内容的高度
  const pageHeight = Math.max(document.body.scrollHeight, document.body.offsetHeight);
  //视窗的高度
  const viewportHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0;
  //隐藏的高度
  const scrollHeight = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
  console.log('scroll');
  if (pageHeight - scrollHeight - viewportHeight <= 0) {
    console.log('底部');
  }
}

我们来看看效果:

是不是惊呆了~(≧∇≦)ノ

当用户滚动页面的时候,会触发节流函数生成定时器,在定时器倒计时结束之前,即使用户不断滚动页面,都不会生成新的定时器,在原定时器倒计时结束之后,会触发滚动函数输出"scroll",并且清除定时器,这时候用户接着滚动页面,就会生成新的定时器,重复以上操作。

ヾ(^▽^*)谢谢大家的阅读,我的能力有限,写的并不是很好,希望大家多多指教!!

参考文章

2019 面试准备 - JS 防抖与节流
防抖和节流原理分析
函数防抖和节流
JS的防抖与节流
函数节流与函数防抖