Wenn ein DOM-Element entfernt wird, werden seine Listener auch aus dem Speicher entfernt?

Antworten:

307

Moderne Browser

Einfaches JavaScript

Wenn ein entferntes DOM-Element referenzfrei ist (keine Verweise darauf), dann ja - das Element selbst wird vom Garbage Collector sowie allen damit verbundenen Event-Handlern / Listenern aufgenommen.

var a = document.createElement('div');
var b = document.createElement('p');
// Add event listeners to b etc...
a.appendChild(b);
a.removeChild(b);
b = null; 
// A reference to 'b' no longer exists 
// Therefore the element and any event listeners attached to it are removed.

Jedoch; Wenn es noch Referenzen gibt, die auf dieses Element verweisen, bleiben das Element und seine Ereignis-Listener im Speicher.

var a = document.createElement('div');
var b = document.createElement('p'); 
// Add event listeners to b etc...
a.appendChild(b);
a.removeChild(b); 
// A reference to 'b' still exists 
// Therefore the element and any associated event listeners are still retained.

jQuery

Es wäre fair anzunehmen, dass die relevanten Methoden in jQuery (wie z. B. remove()) genauso funktionieren würden (wenn remove()man bedenkt , removeChild()dass sie beispielsweise mit geschrieben wurden).

Dies ist jedoch nicht wahr ; Die jQuery-Bibliothek verfügt tatsächlich über eine interne Methode (die nicht dokumentiert ist und theoretisch jederzeit geändert werden kann) mit dem Namen cleanData() (so sieht diese Methode aus ), die beim Entfernen aus dem DOM automatisch alle Daten / Ereignisse bereinigt, die einem Element zugeordnet sind (sei dies über. remove(), empty(), html("")usw.).


Ältere Browser

Ältere Browser - insbesondere ältere Versionen von IE - haben bekanntermaßen Probleme mit Speicherverlusten, da Ereignis-Listener Verweise auf die Elemente behalten, an die sie angehängt wurden.

Wenn Sie eine ausführlichere Erläuterung der Ursachen, Muster und Lösungen zur Behebung älterer Speicherlecks in der IE-Version wünschen, empfehlen wir Ihnen, diesen MSDN-Artikel zum Verständnis und Lösen von Internet Explorer-Leckmustern zu lesen .

Einige weitere relevante Artikel:

Das manuelle Entfernen der Listener selbst wäre in diesem Fall wahrscheinlich eine gute Angewohnheit (nur wenn der Speicher für Ihre Anwendung so wichtig ist und Sie tatsächlich auf solche Browser abzielen).

dsgriffin
quelle
22
Gemäß der jquery-Dokumentation werden bei Verwendung der remove () -Methode für ein Element alle Ereignis-Listener aus dem Speicher entfernt. Dies betrifft das Element selbst und alle untergeordneten Knoten. Wenn Sie die Ereignislistener im Speicher behalten möchten, sollten Sie stattdessen .detach () verwenden. Nützlich, wenn die entfernten Elemente erneut in den Dom eingefügt werden sollen.
Lothre1
1
Wenn das Element untergeordnete Elemente enthält, werden dann auch Ereignislistener für untergeordnete Elemente getrennt?
CBeTJlu4ok
1
@ Lothre1 - das ist nur bei Verwendung der removeMethode. Meistens wird das DOM nur vollständig gelöscht. (wie Turbolinks oder so). Ich frage mich, wie das Gedächtnis beeinflusst wird, wenn ich document.body.innerHTML = ''...
vsync
1
Ich würde mehr als "persönliche Erfahrung" benötigen, eher wie harte Daten und Tests und Links zu Spezifikationen, die besagen, wie der Speicher auf Knoten, die nicht mehr im Dokument enthalten sind, dauerhaft bleibt. Dies ist zu wichtig, um nur einem Wort ohne Beweis zu vertrauen :)
vsync
1
@ Lothre1 Danke - Ich habe etwas tiefer gegraben und herausgefunden, wie sich jQuery in dieser Hinsicht anders verhält als normales JavaScript. Habe die Antwort aktualisiert.
dsgriffin
23

zu jQuery:

Die Methode .remove () entfernt Elemente aus dem DOM. Verwenden Sie .remove (), wenn Sie das Element selbst sowie alles darin entfernen möchten. Zusätzlich zu den Elementen selbst werden alle gebundenen Ereignisse und jQuery-Daten, die den Elementen zugeordnet sind, entfernt. Verwenden Sie stattdessen .detach (), um die Elemente zu entfernen, ohne Daten und Ereignisse zu entfernen.

Referenz: http://api.jquery.com/remove/

jQuery v1.8.2 .remove()Quellcode:

remove: function( selector, keepData ) {
    var elem,
        i = 0;

    for ( ; (elem = this[i]) != null; i++ ) {
        if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
            if ( !keepData && elem.nodeType === 1 ) {
                jQuery.cleanData( elem.getElementsByTagName("*") );
                jQuery.cleanData( [ elem ] );
            }

            if ( elem.parentNode ) {
                elem.parentNode.removeChild( elem );
            }
        }
    }

    return this;
}

anscheinend verwendet jQuery node.removeChild()

Demnach: https://developer.mozilla.org/en-US/docs/DOM/Node.removeChild ,

The removed child node still exists in memory, but is no longer part of the DOM. You may reuse the removed node later in your code, via the oldChild object reference.

Das heißt, Ereignis-Listener werden möglicherweise entfernt, sind jedoch nodenoch im Speicher vorhanden.

Sreenath S.
quelle
3
Sie fügen nur Verwirrung hinzu - jQuery macht nichts, was mit so einfachen removeChildHandlern nicht möglich wäre. Beide geben Ihnen auch eine Referenz zurück, die Sie behalten können, um letztere wieder anzubringen (in diesem Fall bleibt sie offensichtlich im Speicher) oder wegzuwerfen (in diesem Fall wird sie schließlich von GC aufgenommen und entfernt).
Oleg V. Volkov
ich weiß: D. Also, wo bist du derjenige, der die Frage bearbeitet hat? Weil ich hätte schwören können, dass es etwas mit der Verwendung von jquery zu tun hat, um ein DOM-Element zu entfernen, in der Frage zuvor. Jetzt klingt meine Antwort so, als würde ich Dinge erklären, nur um mein Ego zu streicheln. Hey, du kannst immer abstimmen
Sreenath S
8

Zögern Sie nicht, Heap zu beobachten, um Speicherlecks in Ereignishandlern zu sehen, die einen Verweis auf das Element mit einem Abschluss und das Element, das einen Verweis auf den Ereignishandler enthält.

Garbage Collector mag keine Zirkelverweise.

Üblicher Speicherverlustfall: Geben Sie zu, dass ein Objekt einen Verweis auf ein Element hat. Dieses Element hat einen Verweis auf den Handler. Und der Handler hat einen Verweis auf das Objekt. Das Objekt hat Verweise auf viele andere Objekte. Dieses Objekt war Teil einer Sammlung, von der Sie glauben, dass Sie sie weggeworfen haben, indem Sie sie aus Ihrer Sammlung entfernt haben. => Das gesamte Objekt und alles, worauf es verweist, bleibt bis zum Verlassen der Seite im Speicher. => Sie müssen über eine vollständige Kill-Methode für Ihre Objektklasse nachdenken oder beispielsweise einem MVC-Framework vertrauen.

Zögern Sie nicht, den Retention Tree-Teil der Chrome-Entwicklungstools zu verwenden.

lib3d
quelle
8

Nur andere Antworten erweitern ...

Handler für delegierte Ereignisse werden beim Entfernen von Elementen nicht entfernt.

$('body').on('click', '#someEl', function (event){
  console.log(event);
});

$('#someEL').remove(); // removing the element from DOM

Überprüfe jetzt:

$._data(document.body, 'events');
Glückliche Soni
quelle
9
Der Ereignishandler ist an body und nicht an #someEl angehängt. Natürlich sollte der Handler nicht entfernt werden, solange body noch vorhanden ist.
Yangshun Tay
Dies kann manuell entfernt werden: stackoverflow.com/questions/22400907/…
Fangxing
7

Bezüglich jQuery die folgenden gängigen Methoden werden auch andere Konstrukte wie Daten- und Ereignishandler entfernt:

entfernen()

Zusätzlich zu den Elementen selbst werden alle gebundenen Ereignisse und jQuery-Daten, die den Elementen zugeordnet sind, entfernt.

leeren()

Um Speicherverluste zu vermeiden, entfernt jQuery andere Konstrukte wie Daten- und Ereignishandler aus den untergeordneten Elementen, bevor die Elemente selbst entfernt werden.

html ()

Darüber hinaus entfernt jQuery andere Konstrukte wie Daten- und Ereignishandler aus untergeordneten Elementen, bevor diese Elemente durch den neuen Inhalt ersetzt werden.

Jaskey
quelle
2

Ja, der Garbage Collector entfernt sie ebenfalls. Dies kann jedoch bei älteren Browsern nicht immer der Fall sein.

Darin Dimitrov
quelle
7
Ihre Aussage sollte durch API-Dokumentation, Beispiele usw. unterstützt werden
Paolo Fulgoni