记录vue3实现节流、防抖

3,566 阅读1分钟

1. 概念解释

防抖:某一高频事件不断被触发时,仅在最后一次真正执行事件处理代码。

节流:某一高频事件不断被触发时,确保在每一个特定的时间段内被执行一次。

相似点:都是为应对事件持续频繁发生,造成前端性能下降或对后端服务造成的压力。

区别:节流会不断的触发,而防抖仅在最后一次触发。防抖适用于,如搜索输入框提示,仅在输入停止后进行一次提示更新,以减少后台压力。节流适用于,如窗体以拖动的方式调整大小,在每次特定的时间片结束后触发一次窗体大小调整。

2. 实现方式

2.1 函数方式
let debounceTimer: NodeJS.Timeout | null , throttleTimer: NodeJS.Timeout | null

// 防抖
export const debounce = (fn: Function, delay: number) :Function => {
  return (...args: unknown[]) => {
    if (debounceTimer) {
      clearTimeout(debounceTimer);
    }
    debounceTimer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
};
// 节流
export const throttle = (fn: Function, delay: number) :Function => {
  return (...args: unknown[]) => {
    if (throttleTimer) {
      return;
    }
    throttleTimer = setTimeout(() => {
      fn.apply(this, args);
      throttleTimer = null;
    }, delay);
  }
}

测试代码:

<script setup lang="ts">
import { ref } from "vue";
import { debounce, throttle } from "@/common/index.ts";
const conut = ref(0);
const debounceFunc = () => {
  debounce(() => {
    if (conut.value > 0) {
      conut.value--;
    }
  }, 1000)();
};
const throttleFunc = () => {
  throttle(() => {
    conut.value++;
  }, 1000)();
};
</script>

<template>
  <div>
    <el-button type="primary" @click="debounceFunc">-</el-button>
    <div>
      {{ conut }}
    </div>
    <el-button type="primary" @click="throttleFunc">+</el-button>
  </div>
</template>
2.2 自定义指令方式
import { App, DirectiveBinding } from "vue";

let debounceTimer: NodeJS.Timeout | null , throttleTimer: NodeJS.Timeout | null

export default (app: App<Element>) => {
  // 防抖
  app.directive("debounce", {
    mounted(el: HTMLElement, binding: DirectiveBinding) {
    
      const eventType: string =  Object.keys(binding.modifiers)[0] || 'click'
          
      el.addEventListener(eventType, () => {
        
        const dealy: number = binding.arg ? parseInt(binding.arg) : 300;
        const fn: unknown = binding.value;
  
        if (isNaN(dealy)) { 
          throw Error("v-debounce:arg必须为数字!");
        }
        
        if (typeof fn !== "function") {
          throw Error("v-debounce绑定值必须为函数!");
        }

        if (debounceTimer) {
          clearTimeout(debounceTimer);
        }

        debounceTimer = setTimeout(() => {
          fn();
        }, dealy);

      });
    },
  });
  // 节流
  app.directive("throttle", {
    mounted(el: HTMLElement, binding: DirectiveBinding) {
      
      const eventType: string =  Object.keys(binding.modifiers)[0] || 'click'
      
      el.addEventListener(eventType, () => {
        
        const dealy: number = binding.arg ? parseInt(binding.arg) : 300;
        const fn: unknown = binding.value;
  
        if (isNaN(dealy)) { 
          throw Error("v-throttle:arg必须为数字!");
        }
        
        if (typeof fn !== "function") {
          throw Error("v-throttle绑定值必须为函数!");
        }

        if (throttleTimer) {
          return;
        }

        throttleTimer = setTimeout(() => {
          fn();
          throttleTimer = null
        }, dealy);

      });
    },
  });
};

在main.ts中挂载:

import { createApp} from 'vue'
import App from './App.vue'

// 引入全局自定义指令
import directive from "./common/directive"; 

const app = createApp(App);
app.use(directive);
app.mount('#app')

测试代码:

<script setup lang="ts">
import { ref } from "vue";
const conut = ref(0);
const testFunc = (type = '-') => {
    if (type === '-' && conut.value > 0) {
      conut.value--;
    } else if (type === '+') {
      conut.value++;
    }
};
const inputVal = ref('');
const inputFunc = () => {
    console.log(inputVal.value)
};
</script>

<template>
  <div>
    <el-button type="primary" v-debounce:500="testFunc">-</el-button>
    <div>
      {{ conut }}
    </div>
    <el-button type="primary" v-throttle:1000="() => testFunc('+')">+</el-button>
  </div>
  <el-input v-model="inputVal" v-debounce:500.input="inputFunc"/>
</template>