Powrót do strony głównej

Wycieki pamięci w domknięciach JS: jak naprawić

Artykuł omawia mechanizmy wycieków pamięci w domknięciach JavaScript z przykładami kodu i DevTools. Opisano rozwiązania: zerowanie odwołań, usuwanie obsługiwaczy, WeakMap ze słabymi referencjami. Praktyczne instrukcje diagnostyczne.

Wycieki pamięci w domknięciach JS: WeakMap i rozwiązania
Advertisement 728x90

Jak unikać wycieków pamięci w domknięciach JavaScript

Domknięcia w JavaScript przechowują obiekty w pamięci, dopóki istnieją odwołania do funkcji-domknięcia. Prowadzi to do wycieków pamięci, gdy obiekt nie jest już potrzebny, ale moduł zbierający śmieci nie może go usunąć z powodu przechwycenia w domknięciu.

Rozważmy typowy przypadek z dużym obiektem:

let bigData = {
  id: 42,
  data: new Array(1000000).fill('Jakiś dane')
}

function handler(data) {
  return function() {
    console.log(data.id);
    debugger;
  }
}

const runHandler = handler(bigData);
bigData = null;
runHandler();

Wewnętrzna funkcja używa tylko data.id, ale domknięcie przechwytuje cały obiekt bigData. W DevTools w punkcie przerwania widać, że obiekt pozostaje w pamięci.

Google AdInline article slot

Problemy z eval() w domknięciach

Funkcja eval() pogłębia wycieki pamięci, ponieważ silnik dodaje wszystkie zewnętrzne zmienne do domknięcia niezależnie od ich użycia:

let hello = 'Cześć';
let userName = 'Janek';

setTimeout(function () {
  debugger;
  return eval('console.log("Cześć, to Janek!")');
}, 1000);

W DevTools domknięcie zawiera hello i userName, chociaż eval() się do nich nie odwołuje. Dzieje się tak z powodu nieokreśloności kodu w eval(). Unikaj eval() w produkcji.

Metody zapobiegania wyciekom

Przymusowe resetowanie danych

Resetuj przechwycone dane po użyciu:

Google AdInline article slot
function runCalc(buttonId) {
  let bigData = new Array(1000000).fill('Coś');
  const button = document.getElementById(buttonId);

  button.addEventListener('click', function onClick() {
    console.log('Przetworzono elementy:', bigData.length);
    bigData = null;
  });
}

Przypisanie null zwalnia odwołanie wewnątrz domknięcia, pozwalając modułowi zbierającemu usunąć obiekt.

Poprawne usuwanie obsługi zdarzeń

Obsługa zdarzeń to częste źródło wycieków przy usuwaniu elementów DOM:

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);
}

Nazwana funkcja handler pozwala na jej usunięcie wraz z elementem. Anonimowe funkcje strzałkowe nie mogą być usunięte.

Google AdInline article slot

WeakMap dla słabych odwołań

WeakMap rozwiązuje problem przechwytywania dużych obiektów, używając słabych odwołań do kluczy-obiektów.

Klucze tylko obiekty

const weak = new WeakMap();
const key = {};
weak.set(key, 'Tutaj był Janek!');
console.log(weak.get(key));
const anotherKey = {};
console.log(weak.get(anotherKey)); // undefined

Klucze to tylko obiekty lub nie-prymitywy. Dostęp przez get(key) według odwołania.

Słabe odwołania i zbieranie śmieci

Odwołania w WeakMap nie przeszkadzają w zbieraniu śmieci:

const weak = new WeakMap();
let userKey1 = {id: 1};
weak.set(userKey1, 'Janek');
userKey1 = null;

Po wyzerowaniu userKey1 obiekt i wartość są usuwane przez moduł zbierający.

Brak iteratorów

Brak size, forEach, keys(). Tylko podstawowe metody:

  • set(key, value) — ustawianie
  • get(key) — pobieranie
  • has(key) — sprawdzanie
  • delete(key) — usuwanie

Iteratory nie istnieją, aby uniknąć nieprzewidywalności przy zbieraniu śmieci.

Praktyka z WeakMap w domknięciach

Domknięcie z WeakMap nie przechowuje obiektów:

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));

W DevTools po zbieraniu śmieci pozostaje tylko bigData2. WeakMap pozwala automatycznie czyścić pamięć.

Do weryfikacji:

  • Ustaw punkt przerwania w process().
  • Wykonuj kod krok po kroku (F10).
  • Po bigData = null przejdź do zakładki Memory.
  • Wywołaj zbieranie śmieci przyciskiem.
  • Kontynuuj — zobaczysz tylko aktualne klucze.

Co jest ważne

  • Domknięcia przechwytują cały obiekt, nawet jeśli używane jest jedno właściwość.
  • eval() dodaje wszystkie zewnętrzne zmienne do domknięcia.
  • Nazwane obsługi zdarzeń są usuwane przez removeEventListener.
  • WeakMap używa słabych odwołań do kluczy-obiektów.
  • Brak iteratorów w WeakMap dla przewidywalności zbierania śmieci.

— Editorial Team

Advertisement 728x90

Czytaj dalej