JS防抖函数,分步骤 带你实现

83 阅读3分钟

防抖函数的实现

第一步,知道防抖是什么,哪些应用场景。 比如,电梯门的开关,如果一个人要通过了,这个时候电梯在5s内要关闭,如果又来了一个人进去了,电梯会重新开始计时。

我们一般在搜索框输入值,页面的滚动时,页面改变的大小统计时,会触发防抖函数。

第二步,能够手写防抖

如下,此时点击按钮,就会立马触发函数

  <button id="pay">点击付钱</button>
  <script>
    let button = document.querySelector('#pay')
    function payMoney() {
      console.log('已经剁手付钱了');
    }
    button.addEventListener('click', payMoney)
  </script>

第三步,

这里的效果是,页面一刷新,还没有点击,就被调用了。

困惑,为什么一刷新,而不是点击,才被调用呢?因为这里的,debounce()函数是直接执行,里面的func也是直接执行

  <button id="pay">点击付钱</button>
  <script>
    let button = document.querySelector('#pay')
    function payMoney() {
      console.log('已经剁手付钱了');
    }
+    function debounce(func) {
+      func()
+    }
    button.addEventListener('click', debounce(payMoney))

补充知识点,addEventListener的第三个参数是一个options可选的对象:

capture:布尔值,表示回调函数会在该类型的事件捕获阶段传递到eventTarget时才触发

once: 如果为true,只执行一次

第四步,返回一个函数,就能够实现,点击才触发函数

    let button = document.querySelector('#pay')
    function payMoney() {
      console.log('已经剁手付钱了');
    }
    function debounce(func) {
+      return function () {
        func()
      }
    }
    button.addEventListener('click', debounce(payMoney))

第五步,添加时间延迟

尽管已经有了时间延迟,但是每个函数只是延迟触发,并没有时间函数执行的次数限制

    let button = document.querySelector('#pay')
    function payMoney() {
      console.log('已经剁手付钱了');
    }
    function debounce(func, delay) {
      return function () {
        setTimeout(function () {
          func()
+       }, delay)
      }
    }
+   button.addEventListener('click', debounce(payMoney, 1000))

第六步,给定时器设置变量名,在前面清除定时器,实现函数执行次数的限制,

let button = document.querySelector('#pay')
function payMoney() {
  console.log('已经剁手付钱了');
}
function debounce(func, delay) {
  return function () {
+    clearTimeout(timer)
+    timer = setTimeout(function () {
      func()
    }, delay)
  }
}

第七步,上面这样执行会报错,因为不能在定义变量之前,使用变量,“let”关键字

不能这样定义,不然,还是无法限制函数执行的次数。创建了很多个函数块,里面的每个函数块都会有自己的作用域,

let button = document.querySelector('#pay')
function payMoney() {
  console.log('已经剁手付钱了');
}
function debounce(func, delay) {
  return function () {
+    let timer;
    clearTimeout(timer)
    timer = setTimeout(function () {
      func()
    }, delay)
  }
}

修改如下,这里利用了作用域链的机制,是一个闭包的实现,能够实现延迟执行的效果

let button = document.querySelector('#pay')
function payMoney() {
  console.log('已经剁手付钱了');
}
function debounce(func, delay) {
+  let timer
  return function () {
    clearTimeout(timer)
    timer = setTimeout(function () {
      func()
    }, delay)
  }
}

第八步,我们关注this的指向,

this如下指向的window,但是我们希望,按钮点击,this指向的是按钮本身。但是为什么下面的this指向的window,因为回调函数的原因,他触发的时候,已经在windows下面的了。

let button = document.querySelector('#pay')
    function payMoney() {
+      console.log(this); // window
      console.log('已经剁手付钱了');
    }
    function debounce(func, delay) {
      let timer
      return function () {
        clearTimeout(timer)
        timer = setTimeout(function () {
          func()
        }, delay)
      }
    }
    button.addEventListener('click', debounce(payMoney, 1000))

我们可以在函数执行时,提前保存好this

并且使用apply修改this的指向,

    let button = document.querySelector('#pay')
    function payMoney() {
      console.log(this); // window
      console.log('已经剁手付钱了');
    }
    function debounce(func, delay) {
      let timer
      // 有没有想过 this的赋值为啥不写这里?因为这里是debounce的环境,必定是this指向window
      return function () {
        clearTimeout(timer)
+       let context = this
        timer = setTimeout(function () {
+          func.apply(context)
        }, delay)
      }
    }
    button.addEventListener('click', debounce(payMoney, 1000))

第九步,我们考虑函数参数的情况

    function debounce(func, delay) {
      let timer
      // console.log(this); // 这里指向的是window 为什么给button绑定的事件 this指向的是window呢
      return function () {
        clearTimeout(timer)
        let context = this
+        let args = arguments
        timer = setTimeout(function () {
+          func.apply(context, args)
        }, delay)
      }
    }

\