#javascript #dom #события #сборщик_мусора
Возникает необходимость периодически, по средством AJAX, перезаписывать содержимое блока, по средством присвоения нового innerHTML, при этом на дочерние элементы этого блока были назначены обработчики, по средством element.addEventListener, которые я явно не удаляю, и при этом, после добавления элементов, регистрирую ещё и новые. В большинстве случаев, содержимое старого блока, в точности совпадает с новым, но при этом я всё равно его перезаписываю и повторно назначаю обработчики. Меня интересует, при перезаписи дочерних элементов блока, по средством innerHTML удаляются ли также, вместе со всеми дочерними элементами, их объявленные обработчики, или же они остаются вместе с новыми, которые я каждый раз регистрирую по новой? И если да, то происходит ли это также с element.remove() и parent.removeChild(element)?
Ответы
Ответ 1
Ваш случай никоим образом не относится напрямую к dom элементам или к слушателям. Это обычный случай на понимание сборщика мусора или как его называют Garbage Collection или просто GC. Его работа очень проста - если до объекта можно добраться по ссылкам начиная с корневого элемента window, то он не может подвергнуться утилизации. Другими словами, если передать в объект a ссылку на объект b, а затем удалить ссылку на объект a, то объект b его не удержит. let a = { b: null }; let b = { a: a }; a.b = b; a = null; // объект { b: a } будет удален из памяти. Что же происходит, когда мы подписываемся под события. Сначала нужно вспомнить что такое EventDispatcher - export default class EventDispatcher { constructor(){ this.handlers = {}; } addEventListener(type, handler){ let handlerAll = this.handlers[ type ]; if( ! handlerAll){ handlerAll = this.handlers[ type ] = []; } handlerAll.push( handler ); } removeEventListener(type, handler){ let handlerAll = this.handlers[type]; if(handlerAll){ let index = handlerAll.indexOf( handler ); if(index > -1){ handlerAll.splice( index, 1 ); if( ! handlerAll.length) { delete this.handlers[type]; } } } } dispatchEvent(type, event){} } Это очень упрощенный пример, который не содержит комментариев, только потому, что там все до банальности легко. Но особое внимание стоит обратить на то, что передаваемый слушатель просто сохраняется в EventDispatcher. Другими словами - let EventDispatcher = { handlerAll: [ ] }; let handler = function(){}; EventDispatcher.handlerAll.push( handler ); EventDispatcher = null; //все, EventDispatcher канул вне небытия // и handler не смог его удержать. Получается что если ситуация очень простая, почти нереальная, то после удаления объекта из dom он благополучно удалится. Но дело в другом! Остается очень большая вероятность что в памяти останется висеть тот контекст, в котором происходила подписка и это ещё при лучшем стечении обстоятельств. Ведь самая главная причина по которой было одобрено правило хорошего тона "всегда удалять слушатели" является замыкание, которое в javascript является основой. Поэтому я не буду описывать все возможные причины утечек, а лишь ещё раз скажу, что хорошим тоном считается всегда удалять слушатели, да и вообще чистить объекты вручную. Ну а если быть совсем откровенными друг с другом, то нужно понимать что такое Element возвращаемый нам при запросе. когда мы просим выдать нам нужный элемент по какому-то идентификатору, например document.querySelector(#some-id);, программа возвратит нам объект, который представляет из себя совокупность свойств и методов для управления объектом созданным на более низком уровне. И когда Вы пишите так как ниже, то сохраняете ссылку на элемент в переменную - let element = document.querySelector( `#some-id` ); element.addEventListenr( 'click', element_clickHendler ); function element_clickHendler(){} Что в свою очередь при удалении из родителя с помощью любого известного способа, удалит объект лишь из дисплей листа, проще говоря с экрана. Но ссылка так и будет держать объект элемента и слушатель будет в рабочем состоянии, до тех пор пока будет существовать хоть одна ссылка на него. document.querySelector( `#some-id` ).addEventListenr( 'click', element_clickHendler ); function element_clickHendler(){} Если Вы сделаете так как выше, то при удалении элемента любыми известными способами удалит и элемент из дисплей листа и объект элемента. Следовательно и слушатель удалится вместе с объектом элемента. И все это из-за того что ссылка на возвращаемый при поиске объект не сохраняется. Что же касается способа удаления, то больше чем уверен что нет никакой разницы на уровне программы. Ведь объект всегда можно удалить только одним способом, а все остальное разнообразие является синтаксическим сахаром для удобной эксплуатации.
Комментариев нет:
Отправить комментарий