函数防抖与节流

1. 背景

现在的不少项目中都存在类似这样的业务:页面监听鼠标滚轮事件做出对应操作、搜索输入框输入文字时下面弹出联想结果弹框(类似百度输入文字效果)等等,一般情况下会直接绑定scroll事件监听、input事件监听,像用户每次操作页面滚动时,scroll事件触发的频率是很高的,如果在这些函数内部执行了其他函数,尤其是执行了操作DOM的函数,那不仅会造成计算机资源的浪费,还会降低程序运行速度,造成一些体验问题;或者在输入框频繁输入时,频繁调用后台接口,给后台造成太大压力,节流防抖的目的就是在尽量不影响用户体验的情况下,减少监听处理函数的调用次数,提升性能

2. 防抖

2.1 概念

函数防抖指的是,当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时

"函数防抖"的关键之处在于,在一个动作发生一定时间之后,才会执行特定的事件

2.2 简单的实现方案

举一个简单的例子,假设我们要实现鼠标在一个元素上面触发mousemove后鼠标停止移动时,将初始值为0的数字加1,且将加完后的结果显示在div上

<html lang="en">
<style lang="cn">
	#content {
		width: 200px;
		height: 200px;
		background: red;
	}
</style>
<body>
  <div id="content"></div>
  
  <script>
  let num = 1;
  let oDiv = document.getElementById('content');
  let changeNum = function () {
    oDiv.innerHTML = ++num;
  };
    
  // 防抖逻辑
  let deBounce = (fn, delay) => {
    let timer = null;
    return function (...args) {
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout(() => {
		fn(...args);
		timer = null;
      }, delay)
    }
  }
  oDiv.onmousemove = deBounce(changeNum, 500);
  </script>
</body>
</html>

如果不加防抖处理的话,鼠标在div上面不断移动,是会持续不停的触发多次mouseover事件且执行多次回调函数的,加上了防抖之后,实现了鼠标在div上面持续移动都不会执行回调函数,只有当鼠标停下来时才会执行回调函数。

主要的思路就是每次mousemove事件触发时,如果与上次间隔不到delay指定的时间间隔,将会通过clearTimeout去把上一次触发的事件给清除掉,然后再重新设置一个定时器开始计时,直到delay指定的时间完了这段时间内都没有再触发mousemove,才会执行回调函数。

2.3 实际应用场景

例子1:监听窗口尺寸改变事件

$(window).on('resize', debounce(doResizeTimer, 200));

例子2:页面中某个模块,需要根据模块中的内容高度决定是否只展示局部并在底部添加“展开全部”按钮
场景如图所示
在这里插入图片描述

$(this.$refs.descCon).find('img').on(
  'load',
  debounce(() => {
    this.initToggle();
  }, 200)
);

当某个模块中展示的内容是后台返回的一段富文本,里面包含文本、换行符、多张图片,我们需要根据图片渲染完成后,再去计算模块的总高度来决定是否只展示部分内容,剩余的隐藏并且在底部添加"展开全部"按钮,这里需要监听容器里面的img是否加载完成监听load事件,此时就可以用到防抖。

因为这个模块中可能存在多张图片,所以load事件可能触发多次,每次都触发initToggle的话性价比不高,所以使用防抖,起到的作用是如果在200ms内一直有图片触发load事件说明整个模块没完全渲染完成,initToggle函数一直不会被触发,只有当最后一张图片加载完后触发load事件且后续已经没有其他图片再次触发load了,则在200ms之后就会去执行initToggle函数,最理想的状态下,initToggle只会执行一次

注意:防抖的原理是事件在短时间内一直触发的话,回调函数一直不会执行的,所以防抖不太适用于需要实时响应的逻辑,比如页面在滚动时滚动需要实时作出响应,这种场景可能不适用防抖

3. 节流

3.1 概念

函数节流(throttle):规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

3.2 简单的实现方案

还是举一个例子,页面上有一个按钮,我想要在一段时间内,点击N次,只想要执行1次

<html lang="en">
<body>
  <div class="div1">
    <button>防抖按钮</button>
  </div>
  
  <script>
  let btn = document.getElementsByTagName('button')[0];
  let fn = () => console.log('我被触发了!');
    
  // 节流逻辑
  let throttle = function (func, delay) {
    let timer = null;
    let args;
    return function () {
      let context = this;
      args = arguments;
      if (!timer) {
        timer = setTimeout(function () {
          func.apply(context, args);
          timer = null;
        }, delay);
      }
    }
  }
    
  btn.onclick = throttle(fn, 500);
  </script>
</body>

</html>

实现思路是:当触发事件的时候,我们设置一个定时器,再次触发事件的时候,如果定时器存在,就不执行,直到delay时间后,定时器执行回调函数,并且清空定时器,这样就可以设置下个定时器。当第一次触发事件时,不会立即执行函数,而是在delay毫秒后才执行。而后再怎么频繁触发事件,也都是每delay时间才执行一次。

3.3 实际应用场景

例子1:滚动事件监听
模块中显示用户评测,当评测内容过长的时候且点击"展开按钮"显示全部内容时,底下的收起按钮需要做一个吸附在可视区底部的效果,此时需要做一个页面滚动事件监听的处理,当滚动到评测区域快要离开可视区时,取消吸附效果
在这里插入图片描述

$(document).on(
  'scroll',
  throttle(scrollHandler, 200)
);

例子2:搜索输入框输入内容后下面弹出智能匹配内容
在这里插入图片描述

let throttleAjax = throttle(ajax, 1000);

input.addEventListener('keyup', function(e) {
  throttleAjax(e.target.value)
})

4. 相关JS库

项目中需要使用到防抖、节流的操作时,不需要自己造轮子,网上已经有很多现成的方案,类似lodash这样的库已经提供了debounce和throttle方法,如果项目中刚好有这个依赖,可以直接从里面拿出来用。

5. 总结

  • 函数防抖和函数节流都是防止某一时间频繁触发,但是这两个的原理却不一样。
  • 函数防抖是某个时间间隔内不断触发的话就一直不会执行,只有当停止触发才会执行,而函数节流是某个间隔时间内不管触发多少次只执行一次。
使用场景

防抖debounce适用于

  • window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

节流throttle适用于

  • 监听滚动事件,比如是否滑到底部自动加载更多、滚动时对可视区中的元素做特定操作
  • 搜索输入框智能联想,用户在不断输入值时在输入多个字符后再发送请求给后台(也可以使用防抖,看具体产品需求)

6. 参考文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值