Jak předejít únikům paměti v uzávěrech JavaScriptu
Uzávěry v JavaScriptu drží objekty v paměti, dokud existují odkazy na funkci-uzávěr. To vede k únikům, když objekt již není potřeba, ale sběrač odpadu jej nemůže odstranit kvůli zachycení v uzávěru.
Představte si typický případ s velkým objektem:
let bigData = {
id: 42,
data: new Array(1000000).fill('Nějaká data')
}
function handler(data) {
return function() {
console.log(data.id);
debugger;
}
}
const runHandler = handler(bigData);
bigData = null;
runHandler();
Vnitřní funkce používá pouze data.id, ale uzávěr zachycuje celý objekt bigData. V DevTools na bodě přerušení je vidět, že objekt zůstává v paměti.
Problémy s eval() v uzávěrech
Funkce eval() zhoršuje úniky, protože engine přidá všechny externí proměnné do uzávěru bez ohledu na jejich použití:
let hello = 'Ahoj';
let userName = 'Honza';
setTimeout(function () {
debugger;
return eval('console.log("Ahoj to je Honza!")');
}, 1000);
V DevTools uzávěr obsahuje hello a userName, přestože eval() k nim nepřistupuje. To se děje kvůli neurčitosti kódu v eval(). Vyhněte se eval() v produkčním prostředí.
Metody prevence úniků
Nucené resetování dat
Resetujte zachycená data po použití:
function runCalc(buttonId) {
let bigData = new Array(1000000).fill('Něco');
const button = document.getElementById(buttonId);
button.addEventListener('click', function onClick() {
console.log('Zpracováno prvků:', bigData.length);
bigData = null;
});
}
Přiřazení null uvolní odkaz uvnitř uzávěru, což umožní sběrači odpadu odstranit objekt.
Správné odstranění obslužných funkcí událostí
Obslužné funkce událostí jsou častým zdrojem úniků při odstraňování DOM elementů:
function clickSetup(btn, message) {
const bigData = new Array(1000000).fill(message);
function handler() {
console.log(message, bigData.length);
btn.remove();
btn.removeEventListener('click', handler);
}
btn.addEventListener('click', handler);
}
Pojmenovaná funkce handler umožňuje její odstranění spolu s elementem. Anonymní šipkové funkce nelze odstranit.
WeakMap pro slabé odkazy
WeakMap řeší problém zachycení velkých objektů pomocí slabých odkazů na klíče-objekty.
Klíče pouze objekty
const weak = new WeakMap();
const key = {};
weak.set(key, 'Zde byl Honza!');
console.log(weak.get(key));
const anotherKey = {};
console.log(weak.get(anotherKey)); // undefined
Klíče jsou pouze objekty nebo ne-primitiva. Přístup přes get(key) podle odkazu.
Slabé odkazy a sběr odpadu
Odkazy v WeakMap nebrání sběru odpadu:
const weak = new WeakMap();
let userKey1 = {id: 1};
weak.set(userKey1, 'Honza');
userKey1 = null;
Po vynulování userKey1 jsou objekt a hodnota odstraněny sběračem.
Nedostatek iterátorů
Neexistují size, forEach, keys(). Pouze základní metody:
set(key, value)— nastaveníget(key)— získáníhas(key)— kontroladelete(key)— odstranění
Iterátory chybí, aby se zabránilo nepředvídatelnosti při sběru odpadu.
Praxe s WeakMap v uzávěrech
Uzávěr s WeakMap nedrží objekty:
function createProcessor() {
const weakMap = new WeakMap();
return function process(obj) {
weakMap.set(obj, obj.value);
debugger;
return weakMap.get(obj);
}
}
const processor = createProcessor();
let bigData = {
value: 21,
hugeArray: new Array(1000000).fill('*')
};
console.log(processor(bigData));
bigData = null;
let bigData2 = {
value: 42,
hugeArray: new Array(1000000).fill('1')
};
console.log(processor(bigData2));
V DevTools po sběru odpadu zůstává pouze bigData2. WeakMap umožňuje automatické čištění paměti.
Pro kontrolu:
- Nastavte bod přerušení v
process(). - Spouštějte kód krok za krokem (F10).
- Po
bigData = nullpřejděte na kartu Memory. - Zavolejte sběr odpadu tlačítkem.
- Pokračujte — uvidíte pouze aktuální klíče.
Co je důležité
- Uzávěry zachycují celý objekt, i když se používá pouze jedna vlastnost.
eval()přidá všechny externí proměnné do uzávěru.- Pojmenované obslužné funkce událostí se odstraní přes
removeEventListener. WeakMappoužívá slabé odkazy na klíče-objekty.- Neexistují iterátory v
WeakMappro předvídatelnost sběru odpadu.
— Editorial Team
Zatím žádné komentáře.