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>