Czy programy obsługi zdarzeń na węźle DOM są usuwane wraz z węzłem?

(Uwaga: używam jQuery poniżej, ale pytanie jest naprawdę ogólne Javascript jeden.)

Powiedzmy, że mam div#formsection, którego zawartość jest wielokrotnie aktualizowana za pomocą AJAX, Tak:

var formSection = $('div#formsection');
var newContents = $.get(/* URL for next section */);
formSection.html(newContents);

Za każdym razem, gdy aktualizuję ten div, uruchamiam niestandardowe zdarzenie , które wiąże procedury obsługi zdarzeń z niektórymi nowo dodanymi elementami, takimi jak:

// When the first section of the form is loaded, this runs...
formSection.find('select#phonenumber').change(function(){/* stuff */});

...

// ... when the second section of the form is loaded, this runs...
formSection.find('input#foo').focus(function(){/* stuff */});

Więc: wiążę procedury obsługi zdarzeń z niektórymi węzłami DOM, później usuwam te węzły DOM i wstawiam nowe (html() robi to) i powiązanie procesów obsługi zdarzeń z nowymi węzłami DOM.

Czy moje procedury obsługi zdarzeń są usuwane wraz z węzłami DOM, z którymi są powiązane? innymi słowy, gdy Ładuję nowe sekcje, czy wiele bezużytecznych programów obsługi zdarzeń gromadzi się w pamięci przeglądarki, czekając na zdarzenia na węzłach DOM, które już nie istnieją, czy też są usuwane po usunięciu ich węzłów DOM?

Pytanie Dodatkowe: Jak można to samemu przetestować?

Author: Nathan Long, 2010-12-02

5 answers

Funkcje obsługi zdarzeń podlegają takiemu samemu zbieraniu śmieci, jak inne zmienne. Oznacza to, że zostaną one usunięte z pamięci, gdy interpreter stwierdzi, że nie ma możliwości uzyskania odniesienia do funkcji. Samo usunięcie węzła nie gwarantuje jednak usuwania śmieci. Na przykład, weźmy ten węzeł i powiązaną obsługę zdarzeń

var node = document.getElementById('test');
node.onclick = function() { alert('hai') };

Teraz pozwala usunąć węzeł z DOM

node.parentNode.removeChild(node);

Więc node nie będzie już widoczny na Twoim strona internetowa, ale wyraźnie nadal istnieje w pamięci, podobnie jak obsługa zdarzenia

node.onclick(); //alerts hai

Tak długo, jak odniesienie do {[5] } jest nadal dostępne, jego powiązane właściwości (z których onclick jest jednym) pozostaną nienaruszone.

Teraz spróbujmy bez tworzenia zwisającej zmiennej

document.getElementById('test').onclick = function() { alert('hai'); }

document.getElementById('test').parentNode.removeChild(document.getElementById('test'));

W tym przypadku wydaje się, że nie ma dalszej drogi dostępu do węzła DOM #test, więc gdy uruchomiony jest cykl zbierania śmieci, procedura obsługi onclick powinna zostać usunięta z pamięć.

Ale to bardzo prosta sprawa. Korzystanie z zamknięć Javascript może znacznie skomplikować określenie możliwości zbierania śmieci. Spróbujmy powiązać nieco bardziej złożoną funkcję obsługi zdarzeń z onclick
document.getElementById('test').onclick = function() {
  var i = 0;
  setInterval(function() {
    console.log(i++);
  }, 1000);

  this.parentNode.removeChild(this);
};

Więc po kliknięciu #test, element zostanie natychmiast usunięty, jednak sekundę później, a co sekundę później, zobaczysz zwiększoną liczbę wydrukowaną na konsoli. Węzeł jest usuwany, a dalsze odniesienie do niego nie jest możliwe, jednak wygląda na to, że część z nich pozostała. W tym przypadku sama funkcja obsługi zdarzeń prawdopodobnie nie jest zachowana w pamięci, ale utworzony przez nią zakres.

Więc odpowiedź chyba jest; to zależy. Jeśli istnieją zwisające, dostępne odniesienia do usuniętych węzłów DOM, powiązane z nimi procedury obsługi zdarzeń nadal będą znajdować się w pamięci, wraz z resztą ich właściwości. Nawet jeśli tak nie jest, zakres utworzony przez funkcje obsługi zdarzeń może być nadal używany i w pamięci.

W większości przypadki (i szczęśliwie ignorując IE6) najlepiej po prostu zaufać Garbage Collectorowi, aby wykonał swoją pracę, Javascript nie jest przecież C. Jednak w przypadkach, takich jak w poprzednim przykładzie, ważne jest, aby napisać funkcje destruktora jakiegoś rodzaju, aby pośrednio wyłączyć funkcjonalność.

 21
Author: MooGoo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-02 17:51:39

JQuery dokłada wszelkich starań, aby uniknąć wycieków pamięci podczas usuwania elementów z DOM. Tak długo, jak używasz jQuery do usuwania węzłów DOM, usuwanie programów obsługi zdarzeń i dodatkowych danych powinno być obsługiwane przez jQuery. Gorąco polecam lekturę Johna Resiga sekrety Ninja JavaScript Jak idzie w szczegółach na temat potencjalnych wycieków w różnych przeglądarkach i jak biblioteki JavaScript, takie jak jQuery obejść te problemy. Jeśli nie używasz jQuery, na pewno masz aby martwić się wyciekiem pamięci przez osierocone procedury obsługi zdarzeń podczas usuwania węzłów DOM.

 6
Author: James Kovacs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-02 17:56:57

Może być konieczne usunięcie tych procedur obsługi zdarzeń.

Wycieki pamięci Javascript po rozładowaniu strony internetowej

W naszym kodzie, który nie jest oparty na jQuery, ale na jakimś prototypowym dewiancie, mamy inicjalizatory i destruktory w naszych klasach. Stwierdziliśmy, że usuwanie programów obsługi zdarzeń z obiektów DOM jest absolutnie niezbędne, gdy podczas działania niszczymy nie tylko naszą aplikację, ale także poszczególne widżety.

W Przeciwnym Razie skończymy z wyciekami pamięci w IE.

To zaskakująco łatwo uzyskać wycieki pamięci w IE - nawet gdy rozładujemy stronę, musimy być pewni, że aplikacja" zamyka się " czysto, sprzątając wszystko - lub proces IE będzie rosnąć z czasem.

Edit: aby zrobić to poprawnie mamy obserwatora zdarzeń na window dla zdarzenia unload. Kiedy to nastąpi, nasz łańcuch destruktorów zostanie wezwany do prawidłowego oczyszczenia każdego obiektu.

I przykładowy kod:

/**
 * @constructs
 */
initialize: function () {
    // call superclass
    MyCompany.Control.prototype.initialize.apply(this, arguments);

    this.register(MyCompany.Events.ID_CHANGED, this.onIdChanged);
    this.register(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);
},

destroy: function () {

    if (this.overMap) {
        this.overMap.destroy();
    }

    this.unregister(MyCompany.Events.ID_CHANGED, this.onIdChanged);
    this.unregister(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);

    // call superclass
    MyCompany.Control.prototype.destroy.apply(this, arguments);
},
 2
Author: Martin Algesten,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:01

Niekoniecznie

Dokumentacja metody jQuery empty() odpowiada na moje pytanie i daje mi rozwiązanie problemu. Pisze:

Aby uniknąć wycieków pamięci, jQuery usuwa inne konstrukcje, takie jak dane i obsługa zdarzeń z elementów potomnych przed usunięciem elementów siebie.

Więc: 1) jeśli nie zrobilibyśmy tego wprost, dostalibyśmy wycieki pamięci, a 2) używając empty(), mogę tego uniknąć.

Dlatego powinienem zrobić to:

formSection.empty();
formSection.html(newContents);
Wciąż nie jest dla mnie jasne, czy sam by się tym zajął, ale jedna dodatkowa linijka na pewno mi nie przeszkadza.
 2
Author: Nathan Long,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-02 17:49:32

Chciałem poznać siebie, więc po małym teście, myślę, że odpowiedź brzmi tak.

RemoveEvent jest wywoływany, gdy ty .Usuń () coś z DOM.

Jeśli chcesz zobaczyć go samodzielnie, możesz spróbować tego i postępować zgodnie z kodem, ustawiając punkt przerwania. (Używałem jquery 1.8.1)

Dodaj najpierw nowy div:
$('body').append('<div id="test"></div>')

Sprawdź $.cache, aby upewnić się, że nie ma żadnych zdarzeń związanych z nim. (powinien to być ostatni obiekt)

Dołącz Zdarzenie kliknięcia do Informatyka:
$('#test').on('click',function(e) {console.log("clicked")});

Przetestuj i zobacz nowy obiekt w $.cache:
$('#test').click()

Usuń go, a zobaczysz, że obiekt $.cache również zniknął:
$('#test').remove()

 1
Author: user1736525,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-20 04:45:49