Поведение компонентов с динамическим поиском напрямую связано с частотой пользовательского ввода и количеством сетевых запросов. В интерфейсах с автодополнением каждая нажатая клавиша потенциально запускает фильтрацию данных или AJAX-запрос. Без контроля частоты таких операций система быстро перегружается лишними вычислениями и сетевыми обращениями.
В библиотеке Tom Select ключевую роль в оптимизации играет управление частотой вызовов обработчиков поиска и загрузки данных. Для этого используются два фундаментальных подхода: debouncing (дебаунсинг) и throttling (троттлинг). Эти механизмы решают схожую задачу — ограничение частоты выполнения функции, но делают это принципиально разными способами.
При стандартной конфигурации поиска каждый ввод символа инициирует обновление списка:
Если источник данных удалённый, это означает серию HTTP-запросов. При локальной фильтрации — повторную обработку массива данных. В обоих случаях нагрузка растёт линейно от скорости печати пользователя.
Дебаунсинг представляет собой стратегию, при которой функция выполняется только после паузы в действиях пользователя.
Если события происходят часто, таймер постоянно сбрасывается. Выполнение происходит только тогда, когда события перестают поступать.
Формально:
f(t)=где τ — интервал ожидания.
В контексте Tom Select дебаунсинг применяется для сокращения числа запросов при вводе текста в поле поиска.
Логика:
Tom Select позволяет внедрять собственные обработчики загрузки через
load:
new TomSelect('#select', {
valueField: 'id',
labelField: 'title',
searchField: 'title',
load: function(query, callback) {
if (!query.length) return callback();
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => {
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => callback(data))
.catch(() => callback());
}, 300);
}
});
Троттлинг отличается тем, что функция выполняется не после завершения серии событий, а с фиксированным интервалом.
События продолжают поступать, но выполнение происходит не чаще заданного периода.
t_n = nt
где Δt — фиксированный интервал выполнения.
В отличие от дебаунсинга:
new TomSelect('#select', {
valueField: 'id',
labelField: 'title',
searchField: 'title',
load: function(query, callback) {
if (!query.length) return callback();
const now = Date.now();
if (!this.lastRequestTime) {
this.lastRequestTime = 0;
}
const limit = 500;
if (now - this.lastRequestTime < limit) {
return;
}
this.lastRequestTime = now;
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => callback(data))
.catch(() => callback());
}
});
Дебаунсинг:
Троттлинг:
Дебаунсинг:
Троттлинг:
Tom Select использует load как основной механизм
асинхронного поиска. Именно здесь применяются оба подхода.
На практике часто используется гибрид:
new TomSelect('#select', {
load: function(query, callback) {
if (!query.length) return callback();
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => {
const now = Date.now();
const limit = 400;
if (now - (this.last || 0) < limit) return;
this.last = now;
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(r => r.json())
.then(data => callback(data))
.catch(() => callback());
}, 250);
}
});
При использовании статического массива данных применение этих техник также оправдано.
const ts = new TomSelect('#select', {
options: bigDataset,
valueField: 'id',
labelField: 'name',
searchField: 'name',
score: function(search) {
const engine = this.getScoreFunction(search);
return function(item) {
return engine(item);
};
}
});
При необходимости можно внедрить дебаунсинг в пользовательский ввод
через onType:
onType: function(str) {
clearTimeout(this.searchTimer);
this.searchTimer = setTimeout(() => {
this.search(str);
}, 200);
}
Приводит к накоплению вызовов:
setTimeout(() => {
fetch(...)
}, 300);
Без clearTimeout создаётся очередь запросов.
Интервал менее 100 мс приводит к:
Слишком большой интервал делает интерфейс «задержанным»:
При работе через AJAX важно учитывать задержки:
Решение — отмена предыдущих запросов через
AbortController:
load: function(query, callback) {
if (this.controller) {
this.controller.abort();
}
this.controller = new AbortController();
fetch(`/api/search?q=${query}`, {
signal: this.controller.signal
})
.then(r => r.json())
.then(data => callback(data))
.catch(() => callback());
}
Выбор между дебаунсингом и троттлингом влияет не только на производительность, но и на восприятие интерфейса:
В системах с Tom Select этот выбор определяет характер взаимодействия с поиском и частоту обновления списка элементов.