防抖与节流

162 阅读5分钟

节流(Throttling):是指在一段时间内,无论事件被触发多少次,都只执行一次处理函数。(可以理解为攻速,屏幕点烂了也还是按照一定攻速射击)

应用场景举例:鼠标移动、页面滚动;

节流的四种实现方式(其实是两种)

  • 定时器不闭包实现
  • 定时器闭包实现
  • 时间戳不闭包实现
  • 时间戳闭包实现

实现思路:在节流被触发时,判断触发频率是否超过指定时间间隔,如果超过,则执行处理函数,并更新上一次执行处理函数的时间戳;如果没有超过,则忽略该次触发事件。

示例如下

// 定时器不闭包实现
let _time = null;
function throttle(func, delay:1000) {
  if (_time) return
  _timer = setTimeout(() => {
     clearTimeout(_timer)
     _timer = null
     func;
  }, delay)
}

// 定时器闭包实现
function throttle(func, delay:1000) {
  let _time = null;
  return function () {
    let context = this;
    let args = arguments;
     if (_time) return
     _timer = setTimeout(() => {
         clearTimeout(_timer)
         _timer = null
         func.apply(context, args);
      }, delay)
  }
}

// 时间戳不闭包实现
let lastTime = 0;
let now = Date.now();
function throttle(func, delay:1000) {
  if (now - lastTime >= delay) {
    lastTime = now;
    func;
  }
}

// 时间戳闭包实现
function throttle(func, delay:1000) {
  let lastTime = 0;
  return function () {
    let context = this;
    let args = arguments;
    let now = Date.now();
    if (now - lastTime >= delay) {
      lastTime = now;
      func.apply(context, args);
    }
  }
}



防抖(Debouncing):是指在一个事件被频繁触发时,延迟执行处理函数,在最后一次事件触发之后一定时间内,如果没有新的事件触发,则执行该函数。(可以理解为回城读条操作,如果打断了又要重新读条-记住回城的时候不能抖动不然会打断就行)

应用场景举例:搜索框、窗口大小调整;

防抖的两种实现方式(其实是一种)

  • 定时器不闭包实现
  • 定时器闭包实现

实现思路:在函数被触发时,设置一个定时器,如果在定时器的时间范围内该函数再次被触发,则清除定时器并重新设置一个新的定时器,直到定时器时间范围内没有新的事件触发,最后执行该函数。

示例如下

// 定时器不闭包实现
let timer = null;
function debounce(func, delay:1000) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func;
    }, delay);
}

// 定时器闭包实现
function debounce(func, delay:1000) {
  let timer = null;
  return function () {
    let context = this;
    let args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function () {
      func.apply(context, args);
    }, delay);
  }
}




tip*提升

  • addEventListener监听器中使用防抖和节流的注意事项

    • 要注意监听触发的回调funfun()的区别(有无括号),前者是触发函数体,后者是触发函数return的事件(这里就要用闭包了),举例如下:
    function test () {
        console.log("----fun")
        return function () {
            console.log("++++fun()")
        }
    }
    window.addEventListener("scroll", test); // 这里打印----fun
    
    window.addEventListener("scroll", test()); // 这里打印++++fun()
    

  • 在vue项目中怎么将防抖和节流做成元素的自定义指令(v-throttle),如下:

// 自定义指令-节流
<button v-throttle="handleClick">Click me</button>

// 定义 v-throttle 指令
Vue.directive('throttle', {
  bind: function (el, binding) {
    let delay = binding || 500; // 获取指令的参数,默认为 500ms
    let lastTime = 0; // 记录上一次执行的时间
    el.addEventListener('click', function () {
      let now = Date.now(); // 获取当前时间
      if (now - lastTime >= delay) { // 判断当前时间与上一次执行时间的差是否超过指定时间间隔
        binding(); // 执行指令的绑定值,即节流处理函数
        lastTime = now; // 更新上一次执行时间
      }
    })
  }
})


// 自定义指令-防抖
<input type="text" v-debounce="handleInput">

// 定义 v-debounce 指令
Vue.directive('debounce', {
  bind: function (el, binding) {
    let delay = binding || 500; // 获取指令的参数,默认为 500ms
    let timer = null; // 定义定时器
    el.addEventListener('input', function () {
      clearTimeout(timer); // 清除上一次的定时器
      timer = setTimeout(() => {
        binding(); // 执行指令的绑定值,即防抖处理函数
      }, delay);
    })
  }
})

我们要在项目中使用的话,按照如下操作来注册:

// 在utils文件夹下创建一个directiveAPI.js文件
export const throttle = {
  install(Vue) {
    // 定义 v-throttle 指令,上面的复制下来
    XXXXX......
  }
}

export const debounce = {
  install(Vue) {
    // 定义 v-debounce 指令,上面的复制下来
    XXXXX......
  }
}

//注册方法
export default function directivesInstall(Vue) {
  Vue.use(throttle)
  Vue.use(debounce)
}

// 然后再src/main.js下初始化我们需要注册的方法`directivesInstall`
import directivesInstall from '@/utils/directiveMed' // 自定义指令
Vue.use(directivesInstall) //注册指令

tip*提升-2

首先先要说一下为什么用get/set来做防抖和节流

答:

  1. 应用场景:当我们在处理事件、获取元素时就需要用到get/set,所以需要再这些场景下进行防抖和节流就需要用到。
  2. 防抖和节流的条件:普通的防抖和节流是在组件值updata的时候进行,但是用get/set可以在组件挂载的时候进行.
  3. 实现方式:跟tip1中v-on不同

  • Vue2中让对象实现响应式的防抖和节流(proxy或Object.defineProperty)
// 方法定义-防抖
function useDebouncedRef (data, delay = 300) {
    let timer = null
    const value = {value: data} // ?????
    // 创建 proxy 实例
    const proxy = new Proxy(value, {
        get(target, property) {
            // 返回当前值
            return target[property]
        },
        // target:目标, property:属性, newValue 值, receiver:接收者
        set(target, property, newValue, receiver) {
            if(timer != null){
                clearTimeout(timer)
                timer = null
            }
            timer = setTimeout(() => {
                // 修改值
                target[property] = newValue
            }, delay)
            return true;
        }
    })
}
<script>
const text = useDebouncedRef(0, 300) // 这里进行声明
</script>

<template>
  <input v-model="text" />
</template>

  • Vue3中让对象实现响应式的防抖和节流(customRef)

定义:创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。

使用customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 track(收集依赖)trigger(派发更新) 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。

意义为了在组件更新时减少性能瓶颈,可以有效地减少组件更新的频率,提高页面性能。此外,customRef 还可以用于处理事件、获取元素等场景

// 方法定义-防抖
function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track() // 收集依赖
        return value
      },
      // newValue 值
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger() // 派发更新
        }, delay)
      }
    }
  })
}
<script setup>
const text = useDebouncedRef('hello') // 这里进行声明
</script>

<template>
  <input v-model="text" />
</template>

juejin.cn/post/719702…

juejin.cn/post/719615…