Современные пользовательские интерфейсы активно используют события браузера: прокрутку страницы, изменение размера окна, ввод текста в поле, перемещение курсора и многие другие. Некоторые из этих событий генерируются очень часто — десятки и даже сотни раз в секунду.
Например:
input срабатывает при каждом вводе символаscroll может срабатывать десятки раз за одну
секунду прокруткиresize вызывается при каждом изменении размеров
окнаmousemove может вызываться сотни раз в
секундуЕсли при каждом таком событии выполнять сложную логику (запрос к серверу, вычисления, перерисовку компонентов), это может привести к:
Для решения этой проблемы используются техники debounce и throttle. В экосистеме Vue и библиотеке Element Plus эти подходы активно применяются при обработке пользовательских действий.
Debounce — это техника, которая откладывает выполнение функции до тех пор, пока не пройдет определённое время с момента последнего вызова.
Другими словами:
функция выполняется только после того, как события перестали происходить.
Каждый новый вызов сбрасывает таймер.
События:
|----|----|----|----|----|
Debounce (задержка 500ms):
|---Выполнение---|
Функция выполнится один раз, когда поток событий прекратится.
Простейшая реализация:
function debounce(fn, delay) {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
Разбор:
timersetTimeoutПоиск в поле ввода.
Без debounce:
function handleSearch(query) {
fetch(`/api/search?q=${query}`)
}
Если пользователь вводит слово “element”, произойдут запросы:
e
el
ele
elem
eleme
elemen
element
Итого 7 запросов к серверу.
const debouncedSearch = debounce((query) => {
fetch(`/api/search?q=${query}`)
}, 500)
Теперь запрос отправится только один раз после окончания ввода.
Частая ситуация — поле поиска с компонентом ElInput.
<template>
<el-input
v-model="search"
placeholder="Search"
@input="onInput"
/>
</template>
Логика:
import { ref } from 'vue'
const search = ref('')
const onIn put = debounce((value) => {
loadUsers(value)
}, 500)
function loadUsers(query) {
console.log('request:', query)
}
Теперь запрос выполняется только после паузы во вводе.
Компонент ElTable часто используется вместе с
фильтрацией.
Без debounce:
<el-input v-model="filter" @input="applyFilter" />
function applyFilter(value) {
tableData.value = allData.filter(item =>
item.name.includes(value)
)
}
При больших таблицах это создаёт нагрузку.
Использование debounce:
const applyFilter = debounce((value) => {
tableData.value = allData.filter(item =>
item.name.includes(value)
)
}, 300)
Фильтрация выполняется только после паузы.
При использовании watch:
watch(search, debounce((value) => {
fetchUsers(value)
}, 500))
Такой подход часто применяется для:
Throttle ограничивает частоту вызова функции.
Функция выполняется не чаще одного раза за указанный интервал времени, независимо от количества событий.
События:
|----|----|----|----|----|
Throttle (500ms):
|--Выполнение--| |--Выполнение--|
Функция будет выполняться через фиксированные интервалы, даже если события продолжаются.
Простейшая версия:
function throttle(fn, delay) {
let lastCall = 0
return function (...args) {
const now = Date.now()
if (now - lastCall >= delay) {
lastCall = now
fn.apply(this, args)
}
}
}
Логика:
window.addEventListener('scroll', throttle(() => {
console.log('scroll update')
}, 200))
Если пользователь быстро прокручивает страницу, обработчик выполнится максимум раз в 200ms.
Частая задача — реакция на прокрутку контейнера.
Например, бесконечная подгрузка данных в таблице.
const handleScroll = throttle(() => {
if (isNearBottom()) {
loadMore()
}
}, 300)
В шаблоне:
<div @scroll="handleScroll">
При создании больших списков с ElScrollbar события
прокрутки происходят очень часто.
Throttle позволяет уменьшить количество вычислений:
const updateVisibleItems = throttle(() => {
calculateVisibleRange()
}, 100)
Это снижает нагрузку на рендеринг.
| Характеристика | Debounce | Throttle |
|---|---|---|
| Принцип | выполнение после паузы | выполнение через интервалы |
| Реакция на события | только последнее событие | события ограничиваются по частоте |
| Количество вызовов | минимальное | регулярное |
| Основная задача | ожидание окончания действий | контроль частоты |
Подходит для ситуаций, когда важно дождаться окончания действия:
Примеры в интерфейсах Element Plus:
ElInput поискElSelect удалённая загрузкаElTableElFormПодходит для постоянных потоков событий:
Типичные сценарии:
В реальных проектах часто используется библиотека lodash.
Установка:
npm install lodash
Импорт:
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'
Пример debounce:
const handleSearch = debounce((value) => {
loadUsers(value)
}, 400)
Пример throttle:
const handleScroll = throttle(() => {
updateHeader()
}, 200)
Преимущества lodash:
Lodash поддерживает опции:
debounce(fn, wait, options)
Параметры:
leadingtrailingmaxWaitФункция вызывается сразу, а затем блокируется.
debounce(fn, 500, { leading: true })
Функция вызывается в конце задержки.
Это поведение используется по умолчанию.
Гарантирует выполнение функции, даже если события продолжаются.
debounce(fn, 500, {
maxWait: 2000
})
Создание debounce внутри обработчика.
Неправильно:
function onInput(value) {
debounce(() => {
search(value)
}, 300)
}
Каждый вызов создаёт новый debounce.
Правильно:
const debouncedSearch = debounce((value) => {
search(value)
}, 300)
function onInput(value) {
debouncedSearch(value)
}
Lodash предоставляет метод cancel.
const debounced = debounce(loadData, 500)
debounced.cancel()
Полезно при:
Во Vue:
import { onUnmounted } from 'vue'
const debouncedSearch = debounce(loadUsers, 500)
onUnmounted(() => {
debouncedSearch.cancel()
})
Это предотвращает выполнение функции после удаления компонента.
Важно помнить: debounce работает с функциями, а не с реактивными переменными.
Поэтому чаще всего он используется вместе с:
watchПример:
watch(search, debounce((value) => {
fetchUsers(value)
}, 400))
В сложных интерфейсах могут использоваться оба подхода.
Пример:
const searchUsers = debounce(loadUsers, 400)
const handleScroll = throttle(loadMoreUsers, 200)
Такая комбинация позволяет одновременно:
Наиболее частые области применения:
ElInput + debounce
ElTable + debounce
scroll + throttle
resize + throttle
watch(form) + debounce
Использование debounce и throttle значительно снижает нагрузку:
Без оптимизации:
С оптимизацией:
В больших приложениях на Vue и Element Plus эти техники являются стандартным инструментом оптимизации обработки пользовательских событий.