Comments 12
Удаление неиспользуемых обработчиков событий
Удалять обработчик событий назначенный на удаляемый элемент совсем не обязательно. У Вас в setTimeout стоит функция, которая “видит” переменную elem. И этот элемент и обработчик существуют до срабатывания этой функции. И будет существовать дальше - на него есть ссылка из константы elm. А если её как-то убрать, то сборщик мусора удалит и сам элемент.
Что бы убедиться в этом можно и нужно воспользоваться объектом FinalizationRegistry для всех интересующих объектов.
Вот так, к примеру:
window.myGarbage = new FinalizationRegistry((held)=>{
console.log('held\t'+held);
});
function clickSetup(btn, message) {
const bigData = new Array(1000000).fill(message)
myGarbage.register(bigData,'bigData');
myGarbage.register(btn,'btn');
btn.addEventListener('click', () => {
console.log(message, bigData.length)
btn.remove() // Удаляем из DOM
})
}
{
const elem = document.getElementById('myElem')
clickSetup(elem, 'Клик!')
elem.click() // Имитируем клик по элементу
}elem находится в блоке и будет удален. Соответственно DOM объект будет без внешних ссылок и сборщик его прибьёт. Через некоторое время будет об этом сообщение.
elem находится в блоке и будет удален. Соответственно DOM объект будет без внешних ссылок и сборщик его прибьёт. Через некоторое время будет об этом сообщение
Попробовал Ваш код. Сборщик срабатывает прям не быстро, где-то через минуту или даже немного больше. То есть какой-то детерминированности тут нет. И если иметь дело с реальным кодом, а не лайтовыми демонстрациями, приложение будет лагать, пока сборщик не очухается. Но идея интересная, спасибо Вам)
Сборщик срабатывает прям не быстро, где-то через минуту или даже немного больше.
Естественно. Сборщик срабатывает (судя по докам) только в двух случаях:
1. Когда движку может скоро не хватить памяти и надо её почистить.
2. Когда движок некоторое время долго стоит в ожидании.
А вот если сразу после этого кода создать, например, большой при большой массив из реально различных значений, то сборщик мусора сработает сразу после этого кода.
Когда движку может скоро не хватить памяти и надо её почистить
Попробовал изменить Ваш пример добавив ещё один большой массив в конце:
let myGarbage = new FinalizationRegistry((held) => {
console.log('held\t' + held)
})
function clickSetup(btn, message) {
const bigData = new Array(1000000).fill(message)
myGarbage.register(bigData, 'bigData')
myGarbage.register(btn, 'btn')
btn.addEventListener('click', () => {
console.log(message, bigData.length)
btn.remove() // Удаляем из DOM
})
}
{
const elem = document.getElementById('myElem')
clickSetup(elem, 'Клик!')
elem.click()
}
const bigData2 = new Array(100000000).fill(1) // Создаём ещё один большой объект
console.log(bigData2)Страница начинает мощно тупить, комп готовится на взлёт, а сообщения от FinalizationRegistry(), всё так-же появляются с около минутной задержкой
Понятно почему. Массив о-о-очень большой. Это раз. Вызов console.log(bigData2) в данном случае это вообще ресурсоёмкое дело. Это два.
Просто Вы загнали браузер. )))
Вот если бы две последних строки в блок завернули. То, всё было бы мило.
Но, сборщик ведь убил те два объекта?
И добавлю: объект bigData2 никогда не будет удален сборщиком потому, что его использует console.log.
Массив о-о-очень большой
Если ещё один нолик добавить к размеру массива, то владка ложится с сообщением о нехватке памяти. Хотел таким образом спровоцировать сборщик мусора на активную работу. Получилось только браузер нагрузить
Вот если бы две последних строки в блок завернули. То, всё было бы мило
Не совсем вкурил, а как блоки тут влияют?
Но, сборщик ведь убил те два объекта?
Да, разумеется. В примере со вторым большим объектом, иногда при первом открытии страницы, сборщик убивает их практически сразу после завершения загрузки. Но при перезагрузках уже этого воспроизвести не удаётся и они удаляются только спустя какое-то время
В v8 (и наверно не только) всё ещё интереснее:
function make(arr, data) {
console.log(arr.find((item) => item === data));
return () => {}
}
const func = make([], {x: 1});
console.dir(func); // разворачиваем, смотрим [[Scopes]]В замыкание попала data. Это потому, что все упоминаемые во внутренних функциях объекты садятся в одну "лодку", на которую все эти внутренние функции будут ссылаться. И неважно, что (item) => item === data в мусорке, общий объект продолжает жить - на него ссылается func
Это потому, что все упоминаемые во внутренних функциях объекты садятся в одну "лодку"
Пустая внутренняя функция тащит за собой всё окружение родителя без разбору. Прикольно чё. Спасибо Вам)
Ну не совсем всё, arr не попал в копилку. А вот дальше разбираться не стали. Но возможно (хотя и нельзя сказать наверняка), что оптимизирующий компилятор v8 более аккуратно отследит все ссылки и таки выкинет data на мороз, тут не знаю.
Хотя конкретно в этом примере компилятор может не знать дальнейшую судьбу функции, переданной в arr.find, но эффект воспроизводится даже в таком случае, где всё очевидно:
function make(arr, data) {
const cb = () => data;
return () => {}
}
arrне попал в копилку
Точняк. Наверное потому что arr не используется в замыкании, а вот если его там задействовать то он появится в Scopes :
function make(arr, data) {
console.log(arr.find((item) => {
console.log(arr) // Используем аргумент arr
return item === data
}));
return () => {}
}
const func = make([2], {x: 1})
console.dir(func) // Теперь тут будет arrВидимо у всех замыканий внутри функции общее окружение
Утечки памяти в замыканиях JavaScript