js 函数的防抖(debounce)与节流(throttle)

Posted cc-freiheit

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js 函数的防抖(debounce)与节流(throttle)相关的知识,希望对你有一定的参考价值。

  原文:函数防抖和节流;

 

序言:

  我们在平时开发的时候,会有很多场景会频繁触发事件,比如说搜索框实时发请求,onmousemove, resize, onscroll等等,有些时候,我们并不能或者不想频繁触发事件,咋办呢?这时候就应该用到函数防抖和函数节流了!

 

准备材料:

<div id="content" style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>

<script>
    let num = 1;
    let content = document.getElementById(‘content‘);

    function count() {
        content.innerhtml = num++;
    };
    content.onmousemove = count;
</script>

  这段代码, 在灰色区域内鼠标随便移动,就会持续触发 count() 函数,导致的效果如下:

技术图片

  接下来我们通过防抖和节流限制频繁操作。

 

函数防抖(debounce)

  短时间内多次触发同一事件,只执行最后一次,或者只执行最开始的一次,中间的不执行。

// 非立即执行版
function debounce(func, wait) {
    let timer;
    return function() {
      let context = this; // 注意 this 指向
      let args = arguments; // arguments中存着e
        
      if (timer) clearTimeout(timer);

      timer = setTimeout(() => {
        func.apply(this, args)
      }, wait)
    }
}

   我们是这样使用的:

content.onmousemove = debounce(count,1000);

   非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。效果如下:技术图片

 

// 立即执行版
function debounce(func, wait) {
    let timer;
    return function() {
      let context = this; // 这边的 this 指向谁?
      let args = arguments; // arguments中存着e

      if (timer) clearTimeout(timer);

      let callNow = !timer;

      timer = setTimeout(() => {
        timer = null;
      }, wait)

      if (callNow) func.apply(context, args);
    }
}

  立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。用法同上,效果如下:

技术图片

  

// 合成版
/**
   * @desc 函数防抖
   * @param func 目标函数
   * @param wait 延迟执行毫秒数
   * @param immediate true - 立即执行, false - 延迟执行
   */ 
function debounce(func, wait, immediate) {
    let timer;
    return function() {
      let context = this,
          args = arguments;
          
      if (timer) clearTimeout(timer);
      if (immediate) {
        let callNow = !timer;
        timer = setTimeout(() => {
          timer = null;
        }, wait);
        if (callNow) func.apply(context, args);
      } else {
        timer  = setTimeout(() => {
          func.apply
        }, wait)
      }
    }
}

 

 

节流(throttle)

  指连续触发事件但是在 n 秒中只执行一次函数。即 2n 秒内执行 2 次... 。节流如字面意思,会稀释函数的执行频率。

  同样有两个版本,时间戳和定时器版。

// 时间戳版
function throttle(func, wait) {
    let previous = 0;
    return function() {
      let now = Date.now();
      let context = this;
      let args = arguments;
      if (now - previous > wait) {
        func.apply(context, args);
        previous = now;
      }
    }
}

   使用方式如下:

content.onmousemove = throttle(count,1000);

  效果如下:

技术图片

  可以看到,在持续触发事件的过程中,函数会立即执行,并且每 1s 执行一次。

 

// 定时器版
function throttle(func, wait) {
    let timeout;
    return function() {
      let context = this;
      let args = arguments;
      if (!timeout) {
        timeout = setTimeout(() => {
          timeout = null;
          func.apply(context, args)
        }, wait)
      }
    }
}

   用法同上,效果如下:

技术图片

  可以看到,在持续触发事件的过程中,函数不会立即执行,并且每 1s 执行一次,在停止触发事件后,函数还会再执行一次。

  我们应该可以很容易的发现,其实时间戳版和定时器版的节流函数的区别就是,时间戳版的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候。

 

  同样地,我们也可以将时间戳版和定时器版的节流函数结合起来,实现双剑合璧版的节流函数。

/**
 * @desc 函数节流
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param type 1 表时间戳版,2 表定时器版
 */
function throttle(func, wait ,type) {
  if (type === 1) {
    let previous = 0;
  } else if (type === 2) {
    let timeout;
  }
  return function() {
    let context = this;
    let args = arguments;
    if (type === 1) {
        let now = Date.now();

        if (now - previous > wait) {
          func.apply(context, args);
          previous = now;
        }
    } else if (type === 2) {
      if (!timeout) {
        timeout = setTimeout(() => {
          timeout = null;
          func.apply(context, args)
        }, wait)
      }
    }
  }
}

 

以上是关于js 函数的防抖(debounce)与节流(throttle)的主要内容,如果未能解决你的问题,请参考以下文章

js 实现一个debounce防抖函数

JS的防抖与节流 -- springboot实战电商项目mall4j

javascript的防抖和节流

javascript的防抖和节流深入理解

手写Debounce 防抖函数遇到的坑

vue正确的使用函数的防抖与节流