Muss ich Ereignis-Listener entfernen, bevor ich Elemente entferne?

84

Wenn ich ein übergeordnetes Element mit untergeordneten Elementen habe, an die Ereignis-Listener gebunden sind, muss ich diese Ereignis-Listener entfernen, bevor ich das übergeordnete Element lösche? (dh parent.innerHTML = '';) Könnte es zu Speicherlecks kommen, wenn Ereignis-Listener nicht von einem Element getrennt sind, wenn es aus dem DOM entfernt wird?

Alle Arbeiter sind wesentlich
quelle

Antworten:

51

Kurze Antwort: Ja

Lange Antwort: Die meisten Browser behandeln dies korrekt und entfernen diese Handler selbst. Es gibt einige ältere Browser (IE 6 und 7, wenn ich mich richtig erinnere), die dies durcheinander bringen. Ja, es kann zu Speicherlecks kommen. Sie sollten sich darüber keine Sorgen machen müssen, aber Sie müssen sich darum kümmern. Schauen Sie sich dieses Dokument an .

jwueller
quelle
In der Tat: Obwohl die meisten aktuellen Browser nicht so stark darunter leiden, wird IE 7 immer noch häufig verwendet. Schauen Sie sich auch die Speicherleckmuster in JavaScript an .
Marcel Korpel
7
Ist jemand kompetent genug, um dies für den aktuellen Browsermarkt zu aktualisieren? Oder ist das eine gesonderte Frage wert? IE7 dachte ich war ziemlich auslaufen , während ie8 noch rumhängt . Behandelt IE8 verlassene Ereignis-Listener?
Aidan Miles
28
6 Jahre später kann ich davon ausgehen, dass IE < 10sie als veraltet gelten und von niemandem verwendet werden, der zu diesem Zeitpunkt andere Websites als Yahoo und AOL besucht. Jeder, der zu diesem Zeitpunkt unironisch den Internet Explorer verwendet, wird wahrscheinlich eher Opfer eines indischen Telefonbetrugs oder eines Virus, als Probleme mit Ereignishandlern, die die Krabbe eines Browsers verlangsamen.
Braden Best
67

Nur um die Infos hier zu aktualisieren. Ich habe verschiedene Browser getestet, insbesondere auf Speicherverluste für zirkulär abhängige Ereignis-Listener bei iframe-Onload-Ereignissen.

Der verwendete Code (jsfiddle stört den Speichertest, verwenden Sie also Ihren eigenen Server, um dies zu testen):

<div>
    <label>
        <input id="eventListenerCheckbox" type="checkbox" /> Clear event listener when removing iframe
    </label>
    <div>
        <button id="startTestButton">Start Test</button>
    </div>
</div>

<div>
    <pre id="console"></pre>
</div>

<script>

    (function() {
        var consoleElement = document.getElementById('console');
        window.log = function(text) {
            consoleElement.innerHTML = consoleElement.innerHTML + '<br>' + text;
        };
    }());

    (function() {
        function attachEvent(element, eventName, callback) {
            if (element.attachEvent)
            {
                element.attachEvent(eventName, callback);
            }
            else
            {
                element[eventName] = callback;
            }
        }

        function detachEvent(element, eventName, callback) {
            if (element.detachEvent)
            {
                element.detachEvent(eventName, callback);
            }
            else
            {
                element[eventName] = null;
            }
        }

        var eventListenerCheckbox = document.getElementById('eventListenerCheckbox');
        var startTestButton = document.getElementById('startTestButton');
        var iframe;
        var generatedOnLoadEvent;

        function createOnLoadFunction(iframe) {
            var obj = {
                increment: 0,
                hugeMemory: new Array(100000).join('0') + (new Date().getTime()),
                circularReference: iframe
            };

            return function() {
                // window.log('iframe onload called');
                obj.increment += 1;
                destroy();
            };
        }

        function create() {
            // window.log('create called');
            iframe = document.createElement('iframe');

            generatedOnLoadEvent = createOnLoadFunction(iframe);
            attachEvent(iframe, 'onload', generatedOnLoadEvent);

            document.body.appendChild(iframe);
        }

        function destroy() {
            // window.log('destroy called');
            if (eventListenerCheckbox.checked)
            {
                detachEvent(iframe, 'onload', generatedOnLoadEvent)
            }

            document.body.removeChild(iframe);
            iframe = null;
            generatedOnLoadEvent = null;
        }

        function startTest() {
            var interval = setInterval(function() {
                create();
            }, 100);

            setTimeout(function() {
                clearInterval(interval);
                window.log('test complete');
            }, 10000);
        }

        attachEvent(startTestButton, 'onclick', startTest);
    }());

</script>

Wenn kein Speicherverlust vorliegt, erhöht sich der verwendete Speicher nach Ausführung der Tests um maximal 1000 KB. Wenn jedoch ein Speicherverlust auftritt, erhöht sich der Speicher um ca. 16.000 KB. Das Entfernen des Ereignis-Listeners führt immer zu einer geringeren Speichernutzung (keine Lecks).

Ergebnisse:

  • IE6 - Speicherverlust
  • IE7 - Speicherverlust
  • IE8 - kein Speicherverlust
  • IE9 - Speicherverlust (???)
  • IE10 - Speicherverlust (???)
  • IE11 - kein Speicherverlust
  • Kante (20) - kein Speicherverlust
  • Chrome (50) - kein Speicherverlust
  • Firefox (46) - schwer zu sagen, leckt nicht schlecht, also vielleicht nur ineffizienter Müllsammler? Endet ohne ersichtlichen Grund mit zusätzlichen 4 MB.
  • Opera (36) - kein Speicherverlust
  • Safari (9) - kein Speicherverlust

Schlussfolgerung: Bleeding Edge-Anwendungen können wahrscheinlich davonkommen, wenn Ereignis-Listener nicht entfernt werden. Aber ich würde es trotz des Ärgers immer noch als gute Praxis betrachten.

Dwight
quelle