JavaScript 之函数防抖

2019-11-08  本文已影响0人  临安linan

更多个人博客:(https://github.com/zenglinan/blog)

如果对你有帮助,欢迎star。

防抖的原理: 为了避免事件频繁触发执行, 让事件只有在触发后 n 秒才执行, 假如 n 秒内重新触发了事件, 则刷新时间戳, 在下一个 n 秒后执行
使用方式大概是: dom.onmousemove = debounce(getUserAction, 1000);

基础版防抖: 输入指定时间, 实现防抖

function debounce(fn, time){
  let timer
  return function(){
    clearTimeout(timer)
    timer = setTimeout(fn, time)
  }
}

只要在 time 内重新触发了事件, 定时器就会被清除掉, 重新开启下一个。

改良版

上一版实现了基础代码, 但是还有些地方需要完善, 比如:

  1. this 指向被改变了, 需要保证 fn 内部的 this 指向触发元素本身
  2. 需要提供 event 对象

    这里为了保证 this, 采用箭头函数, 同时将 arguments 参数对象传入, 保证 event 对象可以获取到
function debounce(fn, time){
  let timer
  return function(){
    // 事件触发时这里的 this 指向的是元素
    let args = arguments  // 取出的参数对象里包含了 event 对象
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, time)
  }
}

立即执行

以上, 我们的防抖函数已经比较完备了

但是还可以添加一个需求使之更完善:

不要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行

这里用一个可选参数来决定是否要采取这种立即触发的模式

function debounce(fn, time, immediate){
  let timer
  return function(){
    let args = arguments
    clearTimeout(timer)

    if(immediate){  // 采取立即执行模式
      let callNow = !timer  // 只有 timer 为 falsy 值时, 才立即执行
      timer = setTimeout(()=>{
        timer = null  // 只要 time 时间不触发, 就把 timer 设为 null, 那么下一次就会直接执行了
      }, time)
      callNow && fn.apply(this, args)
    }
    
    else{  // 采取传统模式
      timer = setTimeout(() => {
        fn.apply(this, args)
      }, time)
    }
  }
}

思路剖析: 当传入第三个参数为 true 时, 开启立即执行模式, 第一次的时候 timer 肯定为 undefined, 这时候 callNow 为 true, 立即执行

与此同时, 给 timer 赋值定时器, 这时候 timer 就不是 falsy 值了, 如果马上再触发一次, !timer 为false, 不会马上执行

同时, timer 设置的定时器里, time 时间后就会把 timer 重置为 null, 但是每次触发都会清除这个定时器, 所以只要在 time 时间内不去触发, 再下次触发时, !timer 就会为 true, 就能立即执行了

要注意: 定时器返回给 timer 的是数字, 即使清除了定时器, timer 的值也不会被清除, 只有手动赋值才能让其为 falsy 值。

可取消

这里还可以再完善一下, 添加重置 debounce 函数的功能,

假如说 debounce 的时间间隔是 10 s, immediate 为 true, 我只有等 10 秒后才能重新触发事件

我希望有时能够立即重置防抖, 即回到立即执行的状态。

只需添加一个取消函数来重置 timer 即可

function debounce(fn, time, immediate){
  let timer
  var debounceFn = function(){
    let args = arguments
    clearTimeout(timer)

    if(immediate){
      let callNow = !timer
      timer = setTimeout(()=>{
        timer = null
      }, time)
      callNow && fn.apply(this, args)
    }
    
    else{
      timer = setTimeout(() => {
        fn.apply(this, args)
      }, time)
    }
  }
  debounceFn.reset = function(){  // 取消函数, 注意: 取消完后又恢复
    clearTimeout(timer)
    timer = null
  }
  return debounceFn
}

使用的时候需要这样使用

let debounced = debounce(fn, 1000)
dom.onmousemove = debounced
// 重置函数的使用方法
button.onclick = debounced.reset

参考: https://github.com/mqyqingfeng/Blog/issues/22

上一篇下一篇

猜你喜欢

热点阅读