Czy muszę usunąć detektory zdarzeń przed usunięciem elementów?

85

Jeśli mam element nadrzędny z elementami podrzędnymi, do których są przypisane nasłuchiwania zdarzeń, czy muszę je usunąć, zanim wyczyszczę element nadrzędny? (tj. parent.innerHTML = '';) Czy mogą wystąpić przecieki pamięci, jeśli nasłuchiwanie zdarzeń nie jest niezwiązane z elementem, jeśli został usunięty z modelu DOM?

Wszyscy pracownicy są niezbędni
źródło

Odpowiedzi:

51

Krótka odpowiedź: tak

Długa odpowiedź: większość przeglądarek obsługuje to poprawnie i samodzielnie usuwa te programy obsługi. Jest kilka starszych przeglądarek (IE 6 i 7, jeśli dobrze pamiętam), które to psują. Tak, mogą wystąpić wycieki pamięci. Nie powinieneś się tym martwić, ale musisz. Spójrz na ten dokument .

jwueller
źródło
Rzeczywiście: chociaż większość obecnych przeglądarek nie będzie cierpieć z tego powodu tak bardzo, IE 7 jest nadal powszechnie używany. Przyjrzyj się również wzorcom wycieków pamięci w JavaScript .
Marcel Korpel
7
Czy ktoś ma wystarczającą wiedzę, aby zaktualizować to dla obecnego rynku przeglądarek? A może warto osobne pytanie? Wydawało mi się, że IE7 został wycofany , podczas gdy IE8 wciąż się kręci. Czy IE8 obsługuje porzucone nasłuchiwania zdarzeń?
Aidan Miles,
30
Myślę, że 6 lat później IE < 10można to bezpiecznie uznać za przestarzałe i nieużywane przez nikogo, kto odwiedza witryny inne niż Yahoo i AOL w tym momencie. Każdy, kto w tym momencie w sposób unikatowy używa IE, prawdopodobnie padnie ofiarą oszustwa telefonicznego w Indiach lub dostanie wirusa, niż ma problemy z obsługą zdarzeń spowalniającą przeglądarkę.
Braden Best
71

Po prostu zaktualizuj informacje tutaj. Testowałem różne przeglądarki, szczególnie pod kątem wycieków pamięci dla cyklicznie zależnych detektorów zdarzeń w zdarzeniach onload iframe.

Użyty kod (jsfiddle przeszkadza w testowaniu pamięci, więc użyj własnego serwera, aby to przetestować):

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

Jeśli nie ma wycieku pamięci, po wykonaniu testów używana pamięć wzrośnie o około 1000 KB lub mniej. Jeśli jednak nastąpi wyciek pamięci, pamięć zwiększy się o około 16 000 KB. Usunięcie detektora zdarzeń w pierwszej kolejności zawsze powoduje mniejsze zużycie pamięci (brak wycieków).

Wyniki:

  • IE6 - wyciek pamięci
  • IE7 - wyciek pamięci
  • IE8 - brak wycieku pamięci
  • IE9 - wyciek pamięci (???)
  • IE10 - wyciek pamięci (???)
  • IE11 - brak wycieku pamięci
  • Edge (20) - brak wycieku pamięci
  • Chrome (50) - brak wycieku pamięci
  • Firefox (46) - trudno powiedzieć, nie wycieka źle, więc może po prostu nieefektywny garbage collector? Kończy się dodatkowym 4 MB bez wyraźnego powodu.
  • Opera (36) - brak wycieku pamięci
  • Safari (9) - brak wycieku pamięci

Wniosek: aplikacje z najnowszymi rozwiązaniami mogą prawdopodobnie uniknąć usuwania detektorów zdarzeń. Ale mimo irytacji nadal uważam to za dobrą praktykę.

Dwight
źródło