JavaScript 防抖(debounce)、节流(throttle)

1,155 阅读2分钟

防抖(debounce)

如果在N秒内高频执行某个操作会触发一个函数,我们希望在N秒后只执行一次该函数。N秒未到时又执行这个操作,从零开始计时直到N秒结束再执行该函数。

实际场景经常出现在输入框输入文字时触发的回调函数,以此为例:

<input type="text" oninput="change(this)" />
  function change(obj){
    console.log(obj.value)
  }

输入文字时,输入框内的字符每改变一次就实时调用change方法,如果用户连续快速输入,会多次调用目标函数。这种体验当然不好,如果调用的方法里还包含调用了接口,后果可想而知。

关于函数防抖(debounce)的一种简单实现。如下面的代码:

  const debounce = (fn, wait) => {
    let timer = null;
    return function(){
      clearTimeout(timer);
      timer = setTimeout(()=>{
        fn.apply(this, arguments);
      }, wait);
    };
  };

注意: fn.apply(this, arguments);这行代码是把debounce实现中 return 的整体function(){}的参数转移到我们最终要调用的函数fn中。这行代码很关键,它能保证我们通过使用函数防抖debounce包裹的目标函数fn被调用时能正常传参。

使用函数防抖:

<input type="text" oninput="change(this)" />
  const change = debounce(function(obj){
    console.log(obj.value)
  },1000);

html部分没变,注意 change方法的定义,因为防抖函数体 debounce 的实现本身也是闭包的形态,闭包的目的是要把 timer 变量私有化,用来判断时间间隔。

最终调用还是 change(this) 这样,其实它调用了已经被缓存(或者说是被预加载)过的 change 方法。至于 this 的传参,前面说过是 fn.apply(this, arguments); 的功劳。

节流(throttle)

节流也是在N秒内高频执行某个操作,结果是按传入的毫秒数作为时间间隔,连续调用目标函数。

还是要以真实场景为例,比如窗体滚动事件:

<style>
  .father{width:300px;height:300px;overflow:hidden scroll;}
  .child{width:300px;height:2500px;background-color: goldenrod;}
</style>
<div class="father" onscroll="normalScroll(this)">
  <div class="child"></div>
</div>
<script>
  const normalScroll = el => {
	console.log(el);
  }
</script>

以上是普通滚动事件

下面是节流函数调用滚动事件:

<style>
  .father{width:300px;height:300px;overflow:hidden scroll;}
  .child{width:300px;height:2500px;background-color: goldenrod;}
</style>
<div class="father" onscroll="throttleScroll(this)">
  <div class="child"></div>
</div>
<script>
  // 函数节流实现
  const throttle = (fn, wait) => {
    let flag = true;
    return function() {
      if (!flag) return;
      flag = false;
      setTimeout(() => {
        fn.apply(this, arguments);
        flag = true;
      }, wait);
    }
  }
  // 定义函数节流滚动事件
  const throttleScroll = throttle(el => {
    console.log(el);
  },1000);
</script>

与函数防抖用法相同,也是先定义一个预加载函数 throttleScroll,然后二次调用传参。

最后说说使用场景,

关于防抖,我最近常用的是在输入框输入搜索关键字后调用防抖函数(接口)。其实还有鼠标快速移动经过某个按钮或选项卡,或页面窗口缩放事件等使用场景。

节流 上面的例子是页面滚动或局部 div 滚动调用函数。节流区别于防抖是是否持续调用目标函数,防抖只是调用一次目标函数。只要需求满足需要持续调用,我们就用节流。