Comments 7
Думаю лучше использовать Event чтобы не морочиться с хранением слушателей
Вы имеете в виду CustomEvent? Если да, то их нужно диспатчить на уровне window (или document), поскольку потребители могут находиться вообще в разных местах DOM-дерева, и эти события не поймаешь на всплытии/погружении. Поэтому при удалении потребителя, нужно будет принудительно очищать слушатель, чтобы не засорять память. Поэтому где-то хранить слушатели все-таки придется.
на уровне стора диспатчим
function createStore(initial = {}) {
const state = structuredClone(initial);
const emitter = new EventTarget();
function set(partial) {
Object.assign(state, partial);
emitter.dispatchEvent(new CustomEvent('change', { detail: state }));
}
function subscribe(listener) {
const handler = e => listener(e.detail);
emitter.addEventListener('change', handler);
listener(state); // вызвать сразу
return () => emitter.removeEventListener('change', handler);
}
return { get: () => state, set, subscribe };
}подписываемся на стор
subscribe возвращает функцию для отписки
Не пойму, а чем это принципиально отличается от самой первой реализации? Эмиттер неявно будет хранить все слушатели. Остальные проблемы там и остались вроде... Ну и вместо store.value = "abc" нужно будет писать store.set({value: "abc"}).
Принципиальное отличие в том, что когда ты используешь встроенный API событий (EventTarget, addEventListener, dispatchEvent, CustomEvent), ты работаешь через движок браузера, а не через JS-слой. Память под обработчики выделяется внутри C++ части движка, сборщик мусора освободит обработчик автоматически, если emitter или handler больше не достижимы из кода. Даже если ты не вызовешь removeEventListener, при уничтожении самого EventTarget вся его таблица слушателей уничтожается целиком. А это значит что никаких утечек при удалении стора/компонента.
Движки браузеров десятилетиями оптимизируют работу с событиями, там куча всего на самом деле, и основной поток не блокируют и меньше шансов на racecondition, я сам в деталях всё не знаю.
В целом этот тот же pub sub механизм, только реализованный нативно в браузере, зачем писать свою его реализацию не очень понятно , просто какой-то лишний код получается.
О, кажется у нас возник диспут... Это я люблю )
Ну еще мы, кажется, не совсем поняли друг друга, когда говорили об утечках памяти. Я имел в виду утечки при удалении слушателя, когда стор остается.
Вот и дал демку с удалением output, когда через некоторое время надпись в консоли исчезает, поскольку сборщик мусора отработал.
А это значит что никаких утечек при удалении стора/компонента.
К сожалению, относительно функции прослушки это не так. Вот я создал демку. При удалении слушателя не удаляется функция. Только вручную, поскольку ссылка на слушатель (переменнаяoutput, сохраненная в замыкании) так и хранится в памяти. В консоли постоянная ошибка Cannot set properties of null (setting 'textContent'). Что полностью совпадает с поведением самой первой реализации.
Если удалить стор, слушатели удалятся в обоих случаях.
За использование EventTarget отдельный респект и уважуха )
Да действительно кажется не поняли друг друга.
Я советую только лишь избавиться в вашем коде от .forEach по коллбэкам и использовать нативный event emmiter, остальное(proxy/weakref) оставить как есть, а пример кода который я прислал - он вообще совсем другой и я хотел им показать только то как используется eventTarget
Веб. К черту фреймворки! Пишем свой starter-kit с роутером и сторами. Часть 2