Zpět na domů

Úniky paměti v uzávěrech JS: jak opravit

Článek rozebírá mechanismy úniků paměti v uzávěrech JavaScript s příklady kódu a DevTools. Jsou popsána řešení: nulování odkazů, odstranění posluchačů událostí, WeakMap se slabými odkazy. Praktické pokyny pro diagnostiku.

Úniky paměti v uzávěrech JS: WeakMap a řešení
Advertisement 728x90

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.

Google AdInline article slot

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í:

Google AdInline article slot
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.

Google AdInline article slot

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) — kontrola
  • delete(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 = null př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.
  • WeakMap používá slabé odkazy na klíče-objekty.
  • Neexistují iterátory v WeakMap pro předvídatelnost sběru odpadu.

— Editorial Team

Advertisement 728x90

Číst dál