函数防抖和节流

在前端开发的过程当中,咱们常常会须要绑定一些持续触发的事件,如 resize、scroll、mousemove 等等,但有些时候咱们并不但愿在事件持续触发的过程当中那么频繁地去执行函数。html

一般这种状况下咱们怎么去解决的呢?通常来说,防抖和节流是比较好的解决方案。前端

让咱们先来看看在事件持续触发的过程当中频繁执行函数是怎样的一种状况。git

html 文件中代码以下github

<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;
    const content = document.getElementById('content');
    function count() {
      content.innerHTML = num++;
    };
    content.onmousemove = count;
  </script>
复制代码

在上述代码中,div 元素绑定了 mousemove 事件,当鼠标在 div(灰色)区域中移动的时候会持续地去触发该事件致使频繁执行函数。效果以下bash

能够看到,在没有经过其它操做的状况下,函数被频繁地执行致使页面上数据变化特别快。因此,接下来让咱们来看看防抖和节流是如何去解决这个问题的。markdown

防抖(debounce)

所谓防抖,就是指触发事件后 n 秒后才执行函数,若是在 n 秒内又触发了事件,则会从新计算函数执行时间。app

防抖函数分为非当即执行版和当即执行版。函数

非当即执行版:oop

function debounce(func, wait) {
  let timeout;
  return function () {
    const context = this;
    const args = [...arguments];
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(context, args)
    }, wait);
  }
}
复制代码

非当即执行版的意思是触发事件后函数不会当即执行,而是在 n 秒后执行,若是在 n 秒内又触发了事件,则会从新计算函数执行时间。this

咱们依旧使用上述绑定 mousemove 事件的例子,经过上面的防抖函数,咱们能够这么使用

content.onmousemove = debounce(count,1000);
复制代码

效果以下

能够看到,在触发事件后函数 1 秒后才执行,而若是我在触发事件后的 1 秒内又触发了事件,则会从新计算函数执行时间。

上述防抖函数的代码还须要注意的是 this 和 参数的传递

const context = this;
const args = [...arguments];
复制代码

防抖函数的代码使用这两行代码来获取 this 和 参数,是为了让 debounce 函数最终返回的函数 this 指向不变以及依旧能接受到 e 参数。

当即执行版:

function debounce(func,wait) {
  let timeout;
  return function () {
      const context = this;
      const args = [...arguments];
      if (timeout) clearTimeout(timeout);
      const callNow = !timeout;
      timeout = setTimeout(() => {
          timeout = null;
      }, wait)
      if (callNow) func.apply(context, args)
  }
}
复制代码

当即执行版的意思是触发事件后函数会当即执行,而后 n 秒内不触发事件才能继续执行函数的效果。

使用方法同上,效果以下

在开发过程当中,咱们须要根据不一样的场景来决定咱们须要使用哪个版本的防抖函数,通常来说上述的防抖函数都能知足大部分的场景需求。但咱们也能够将非当即执行版和当即执行版的防抖函数结合起来,实现最终的双剑合璧版的防抖函数。

双剑合璧版:

/**
 * @desc 函数防抖
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param immediate true 表当即执行,false 表非当即执行
 */
function debounce(func, wait, immediate) {
  let timeout;
  return function () {
    const context = this;
    const args = [...arguments];
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      const callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, wait)
      if (callNow) func.apply(context, args)
    }
    else {
      timeout = setTimeout(() => {
        func.apply(context, args)
      }, wait);
    }
  }
}
复制代码

节流(throttle)

所谓节流,就是指连续触发事件可是在 n 秒中只执行一次函数。 节流会稀释函数的执行频率。

对于节流,通常有两种方式能够实现,分别是时间戳版和定时器版。

时间戳版:

function throttle(func, wait) {
    var 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)
            }
        }

    }
}
复制代码

参考文章:

github.com/mqyqingfeng…

github.com/mqyqingfeng…