Tom Select строится вокруг событийной модели, в которой почти каждое действие пользователя и внутреннее изменение состояния сопровождается генерацией события. Это делает библиотеку гибкой, но одновременно требует аккуратного управления жизненным циклом обработчиков.
Любая подписка на события, которая не была корректно удалена, остаётся в памяти и продолжает реагировать на изменения, даже если соответствующий экземпляр селекта уже не используется. Это приводит к утечкам памяти, дублированию логики и неожиданным побочным эффектам.
В экземпляре Tom Select события регистрируются через метод
on, принимающий имя события и функцию-обработчик:
const select = new TomSelect('#select', {
onChange(value) {
console.log('Изменение значения:', value);
}
});
Альтернативный способ — явная регистрация через API:
select.on('change', (value) => {
console.log(value);
});
Каждый такой обработчик сохраняется внутри внутреннего реестра экземпляра и вызывается при наступлении соответствующего события.
offОсновной механизм удаления подписки — метод off. Он
позволяет удалить ранее зарегистрированный обработчик, полностью
исключив его из цепочки вызовов.
function handleChange(value) {
console.log('change:', value);
}
select.on('change', handleChange);
// позднее
select.off('change', handleChange);
Ключевой момент заключается в том, что ссылка на функцию должна совпадать. Анонимные функции невозможно корректно удалить:
// Плохая практика
select.on('change', (value) => {
console.log(value);
});
// удалить невозможно, так как нет ссылки
Использование inline-обработчиков приводит к невозможности точечной отписки. В системах с динамическим созданием и уничтожением экземпляров это становится источником накопления “мертвых” подписок.
Корректный подход:
const onSea rch = (query) => {
console.log('поиск:', query);
};
select.on('search', onSearch);
// позже
select.off('search', onSearch);
В некоторых сценариях требуется полностью очистить все обработчики,
связанные с конкретным событием. Для этого off может
использоваться без указания функции:
select.off('change');
Такой вызов удаляет все подписки на событие change,
независимо от их количества и источника регистрации.
При уничтожении или переинициализации интерфейса требуется удалить
все события сразу. В Tom Select это достигается через вызов
destroy, который выполняет комплексную очистку внутреннего
состояния:
select.destroy();
После выполнения:
Использование destroy является обязательным при
динамическом создании селектов в SPA-подобных архитектурах.
В приложениях, где элементы создаются и удаляются часто, неправильное управление подписками приводит к накоплению обработчиков:
function createSelect() {
const select = new TomSelect('#select');
select.on('change', (value) => {
console.log(value);
});
return select;
}
Если такой селект пересоздаётся без вызова destroy,
предыдущие обработчики остаются активными, даже если DOM-элемент
удалён.
Правильная модель:
let select = new TomSelect('#select');
function recreate() {
if (select) {
select.destroy();
}
select = new TomSelect('#select');
}
Иногда требуется временная подписка, которая сама себя удаляет после первого срабатывания:
function onceHandler(value) {
console.log('одноразовое событие:', value);
select.off('change', onceHandler);
}
select.on('change', onceHandler);
Такой подход полезен при инициализации зависимых компонентов или одноразовой синхронизации состояния.
В сложных интерфейсах обработчики часто включаются и выключаются в зависимости от состояния приложения:
let enabled = true;
function handler(value) {
if (!enabled) return;
console.log(value);
}
select.on('change', handler);
// отключение логики без удаления подписки
enabled = false;
Однако более чистый подход — полное удаление и повторное добавление:
select.off('change', handler);
select.on('change', handler);
На практике встречаются повторяющиеся проблемы:
Потеря ссылки на функцию
select.on('change', function(value) {});
select.off('change', function(value) {}); // не сработаетДвойная регистрация обработчиков
on без offОтсутствие destroy при удалении DOM
Смешивание контекстов this
При использовании методов объекта важно сохранять контекст:
class Controller {
constructor(select) {
this.select = select;
this.onCha nge = this.onChange.bind(this);
this.select.on('change', this.onChange);
}
onChange(value) {
console.log(this, value);
}
destroy() {
this.select.off('change', this.onChange);
}
}
Без bind ссылка на функцию будет другой, и отписка
станет невозможной.
В динамических интерфейсах часто требуется переинициализация без утечек:
function initSelect() {
if (window.selectInstance) {
window.selectInstance.destroy();
}
window.selectInstance = new TomSelect('#select');
const handler = (value) => {
console.log(value);
};
window.selectInstance.on('change', handler);
window.selectInstance._handler = handler;
}
Сохранение ссылки на обработчик позволяет гарантированно удалить его при следующей инициализации.
Устойчивые архитектуры используют несколько принципов:
offdestroy при удалении компонентаПосле вызова off или destroy экземпляр
перестаёт вызывать соответствующие обработчики. Однако сам объект
селекта может оставаться валидным, если не был уничтожен полностью. Это
позволяет гибко управлять повторным использованием экземпляров без
пересоздания DOM-структуры.