什么是防抖、节流!
防抖(Debouncing)和节流(Throttling)是两种用于控制函数触发频率的常见前端开发技巧,它们的主要目的是减少函数被频繁触发的情况,以提高性能和用户体验。
防抖:
防抖是指在一段时间内,多次触发同一事件后,只执行一次相应的函数。具体来说,如果在规定的时间间隔内多次触发事件,那么防抖会将这些事件合并为一次,并在延迟结束后执行相应的函数。防抖常用于处理用户输入,如搜索框自动补全或搜索建议,以减少不必要的请求和响应。
举个例子:当用户在搜索框中输入文字时,防抖可以确保只在用户停止输入一段时间后才触发搜索请求,而不是在每次按键时都触发请求。
function debounce(fn, time) {
let timer = null
return function (...args) {
if (timer) {
clearTimeout(timer)
timer = null
}
timer = setTimeout(() => {
fn(...args)
clearTimeout(timer)
timer = null
}, time)
}
}
const input = document.querySelector('#kw')
const onInput = debounce(function (e) {
console.log(e, 'debounce')
}, 1000)
input.addEventListener('input', onInput)
节流:
节流是指在一段时间内,将多次触发的事件均匀分布执行相应的函数,而不会重复执行。具体来说,事件触发后,函数会执行,然后等待一段时间,在这段时间内不管事件触发多少次,都不会再次执行,只有等到这段时间结束后才会再次执行函数。节流常用于控制事件的触发频率,以避免事件处理过于频繁。
举个例子:当用户滚动页面时,节流可以确保在每隔一定时间才触发页面滚动事件的处理函数,而不是每次滚动都触发。
function throttle(fn, time) {
let flag = true
return function (...args) {
if (!flag) {
return
}
flag = false
let timer = setTimeout(() => {
fn(...args)
flag = true
clearTimeout(timer)
timer = null
}, time)
}
}
const input = document.querySelector('#kw')
const onInput = throttle(function (e) {
console.log(e, 'throttle')
}, 3000)
input.addEventListener('input', onInput)
如何在vue中通过自定义指令方式使用防抖呢?
- 1、在directive目录下新建inputDirective.ts,代码如下
const InputTagName = ['INPUT', 'TEXTAREA']
function compositionStart(event: CompositionEvent & { target: { composing: boolean } }) {
event.target.composing = true
}
function compositionEnd(event: CompositionEvent & { target: { composing: boolean } }) {
event.target.composing = false
// 发送一个事件出去,确保当输入法组合事件结束时,文本框内的内容能及时更新
const e = new Event('input', { bubbles: true })
event.target.dispatchEvent(e)
}
// 找到input元素:兼容当指令绑定到组件上时
function findInput(el: HTMLElement): HTMLElement | null {
const queue: HTMLElement[] = []
queue.push(el)
while(queue.length > 0) {
const cur = queue.shift()
if(InputTagName.includes(cur.tagName)) {
return cur
}
if(cur?.childNodes) {
// @ts-ignore
queue.push(...cur.childNodes)
}
}
return null
}
function isFuncton(value: any) {
return Object.prototype.toString.call(value) === '[object Function]'
}
function debounce(input: (event: Event & { target: { composing: boolean } } ) => any, time: number): (this: HTMLElement, event: Event) => any {
// @ts-ignore
let timer: string | number | NodeJS.Timeout | undefined
return function(event: Event & { target: { composing: boolean } }) {
if(event.target.composing) {
return
}
if(timer) {
clearTimeout(timer)
timer = undefined
}
timer = setTimeout(() => {
input(event)
clearTimeout(timer)
timer = undefined
}, time)
}
}
let functionDebounce : (this: HTMLElement, event: Event) => any
export default {
mounted(el: HTMLElement & { _INPUT: HTMLElement | null}, binding: any) {
const { value, arg } = binding
if(value && isFuncton(value)) {
let timeout = 600
if(arg && Number.isNaN(arg)) {
timeout = Number(arg)
}
functionDebounce = debounce(value, timeout)
const input = findInput(el)
el._INPUT = input
if(input) {
input.addEventListener('input', functionDebounce)
input.addEventListener('compositionstart', compositionStart)
input.addEventListener('compositionend', compositionEnd)
}
}
},
beforeUnmount(el: HTMLElement & { _INPUT: HTMLElement | null}) {
if(el._INPUT) {
el._INPUT.removeEventListener('input', functionDebounce)
el._INPUT.removeEventListener('compositionstart', compositionStart)
el._INPUT.removeEventListener('compositionend', compositionEnd)
el._INPUT = null
}
},
}
- 2、把自定义指令进行全局注册,在main.ts中修改如下:
const app = createApp(App)
// 全局注册
app.directive('inputDebouce', inputDebouce)
app.mount('#app')
- 3、在组件中使用:
<template>
<el-input
type="textarea"
v-model="internalValue"
v-inputDebouce:1000="inputDebouce"
/>
</template>
<script setup lang="ts">
const inputDebouce = (e: Event) => {
console.log(e)
}
</script>