你真的了解防抖吗?

111 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

防抖

触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次触发,则重新计算时间。

版本【一】:基础功能

  • 每次触发事件时,都取消之前的延时调用方法
function debounce(fn, wait = 500) { // wait:防抖时间,默认500ms
  let timer = null;
  return function() {
    if(timer) clearTimeout(timer); // 每当函数调用的时候,把前一个定时器清理掉
    timer = setTimeout(func, wait)
  }
}

版本【二】:绑定this

  • 如果我们需要做防抖处理的函数,内部使用了 this 的话,根据 “回调函数中的 this 指向 window” ,则 fn 函数中 的 this 指向会出问题

我们使用 apply 来解决这个问题

function debounce(fn, wait = 500) {
  let timer = null;
  return function() {
    if(timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this); // 这里使用了箭头函数,所以这里的 this 和 return function 中的 this 相同
    }, wait)
  }
}
function sayHi() {
  console.log("防抖");
}
var input = document.getElementById("input");
// 我们最终调用的时候,是绑定的 debounce 返回的函数,所以 debounce “返回的函数” 中 this 指向 input 的DOM结构
input.addEventListener("input", debounce(sayHi)); // 防抖

版本【三】:传参

  • 我们自定义的事件可能会有参数传递
  • js原生的事件处理函数会传递 event 对象
function debounce(fn, wait = 500) {
  let timer = null;
  return function(...args) { // 在这里捕获入参
    if(timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args); // 传递给 apply 的第二个参数
    }, wait)
  }
}

版本【四】:立即执行

有的时候,我们不希望等到事件停止触发后才执行,而是希望首次触发的时候立即执行,而后再进行防抖逻辑

function debounce(fn, wait = 500, immediate = false) { // 使用immediate判断是否需要立即执行
  let timer = null;
  return function(...args) {
    if(timer) clearTimeout(timer);
    if(immediate) {
      // 如果已经执行过了,则不需要再次执行
      let canRun = !timer;
      timer = setTimeout(() => {
        timer = null; // 将定时器设置为 null,高频调用函数结束(短时间内的高频结束了)。保证下一次“高频调用”出现时,可以再次“立即执行”
      }, wait)
      // 如果 timer 当前为 null,则说明这是本次“高频调用”的第一次执行,那么就“立即执行”
      if(canRun) fn.apply(this, args);
    } else { 
      // 原先的逻辑
      timer = setTimeout(() => {
        fn.apply(this, args);
      }, wait)
    }
  }
}

版本【五】:取消函数执行

假设我们的防抖时间间隔是 5s,但是在这 5s 内(函数尚未执行),我突然不想让这个函数执行了(点错了?),那么应该如何处理呢?

debounce 函数目前返回了一个函数,那么我们可以考虑给这个函数上再挂载一个 cancel 函数

  • 将之前的代码改造为如下形式(效果一致)
function debounce(fn, wait = 500, immediate = false) {
  let timer = null;
  let debounced = function(...args) {
    if(timer) clearTimeout(timer);
    if(immediate) {
      let canRun = !timer;
      timer = setTimeout(() => {
        timer = null;
      }, wait)
      if(canRun) fn.apply(this, args);
    } else { 
      timer = setTimeout(() => {
        fn.apply(this, args);
      }, wait)
    }
  }
  return debounced
}
  • 再对 debounced 进行挂载额外的函数
function debounce(fn, wait = 500, immediate = false) {
  let timer = null;
  let debounced = function(...args) {
    if(timer) clearTimeout(timer);
    if(immediate) {
      let canRun = !timer;
      timer = setTimeout(() => {
        timer = null;
      }, wait)
      if(canRun) fn.apply(this, args);
    } else { 
      timer = setTimeout(() => {
        fn.apply(this, args);
      }, wait)
    }
  }
  
  debounced.cancel = function() {
    clearTimeout(timer); // 清除定时器
    timer = null; // 方便下次“立即执行”
  }
  
  return debounced
}
  • 调用 debounce
function sayHi() {
  console.log("防抖");
}
var input = document.getElementById("input");
var debounceSayHi = debounce(sayHi);
input.addEventListener("input", debounceSayHi);

// 取消操作
var button = document.getElementById("button");
button.addEventListener("click", function() {
  debounceSayHi.cancel();
})