防抖和节流

274 阅读3分钟

前端开发中,经常会绑定一些持续触发的事件,例如resize、scroll、mousemove等等,但有些时候我们并不需要在事件持续触发的情况下那么频繁地去执行函数。 因此,防抖和节流应运而生。 接下里放一段在事件持续触发频繁调用函数地栗子:

<div class="tag" 
     style="width: 100%;height: 80px; line-height: 80px;
     text-align: center;font-size: 40px;background-color: #cccccc;
     color: white;">
</div>
<script>
let tag = document.getElementsByClassName('tag')[0];
let countNum = 1;
function count() {
  tag.innerHTML = countNum++;
}
</script>

防抖(debounce)

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

防抖函数立即执行版

// 事件处理函数
function count() {
  tag.innerHTML = countNum++;
}
// 防抖函数
function debounce(func, delay) {
  let timeout;
  return function() {
    const context = this;
    const args = [...arguments];
    if(timeout) clearTimeout(timeout);
    if(!timeout) {
      func.apply(context, args);
    }
    timeout = setTimeout(() => {
      timeout = null;
    }, delay);
  }
}
tag.onmousemove = debounce(count, 500);

结果:当事件开始触发,会立即执行事件处理函数,如在n秒内,重复触发事件,然后n秒内不触发事件才能继续执行函数地效果。

防抖函数非立即执行版

// 事件处理函数
function count() {
  tag.innerHTML = countNum++;
}
function debounce(func, delay) {
  let timeout;
  return function() {
    const context = this;
    const args = [...arguments];
    if(timeout) clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  }
}
tag.onmousemove = debounce(count, 500);

结果:当事件开始触发后500ms后执行,如果在触发事件后地500ms内又触发了事件,则会重新计算函数执行时间

在开发过程中,我们可以根据不同的场景来决定使用哪一个版本的防抖函数更加恰当。因此可以将立即执行版和非立即执行版结合起来。

防抖结合版:

/**
 * @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) {
      if (!callNow) func.apply(context, args)
      timeout = setTimeout(() => {
        timeout = null;
      }, wait)
    }
    else {
      timeout = setTimeout(() => {
        func.apply(context, args)
      }, wait);
    }
  }
}

节流(throttle)

所谓节流,就是当事件持续触发的时候,按一定的事件频率去执行处理函数,稀释触发的频率。

节流时间戳版:

// 事件处理函数
function count() {
  tag.innerHTML = countNum++;
}
function throttle(func, wait) {
  let previous = 0;
  return function() {
    let now = Date.now();
    let context = this;
    let args = arguments;
    if(now- previous > wait) {
      previous = now;
      func.apply(context, args);
    }
  }
}
tag.onmousemove = throttle(count, 500);

结果:当事件不断触发的时候,每500ms后,才执行一次处理函数

节流定时器版:

// 事件处理函数
function count() {
  tag.innerHTML = countNum++;
}
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)
    }
  }
}
tag.onmousemove = throttle(count, 500);

结果:可以看到,在持续触发事件的过程中,函数不会立即执行,并且每 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)
            }
        }

    }
}

参考文章:juejin.cn/post/684490…
备注:本篇文章仅用于个人笔记记录。