Javascript 在删除元素之前是否需要删除事件侦听器?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6033821/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 19:57:56  来源:igfitidea点击:

Do I need to remove event listeners before removing elements?

javascriptdommemory-leaksdom-events

提问by All Workers Are Essential

If I have a parent element with children who have event listeners bound to them, do I need to remove those event listeners before I clear the parent? (i.e., parent.innerHTML = '';) Could there be memory leaks if event listeners are not unbound from an element if it's removed from the DOM?

如果我有一个父元素,其子元素绑定了事件侦听器,我是否需要在清除父元素之前删除这些事件侦听器?(即,parent.innerHTML = '';)如果事件侦听器未从元素中解除绑定(如果它已从 DOM 中删除),是否会发生内存泄漏?

采纳答案by jwueller

Short answer: yes

简短回答:是的

Long answer: Most browsers handle this correctly and remove those handlers themselves. There are some older browsers (IE 6 and 7, if i recall correctly) that are messing this up. Yes, there could be memory leaks. You should not have to worry about this, but you need to. Have a look at this document.

长答案:大多数浏览器都会正确处理这个问题并自己删除这些处理程序。有一些较旧的浏览器(IE 6 和 7,如果我没记错的话)将其搞砸了。是的,可能存在内存泄漏。您不必担心这一点,但您需要这样做。看看这个文件

回答by Dwight

Just to update the info here. I've been testing various browsers, specifically for memory leaks for circularly dependent event listeners on iframe onload events.

只是为了更新这里的信息。我一直在测试各种浏览器,特别是针对 iframe onload 事件上循环依赖事件侦听器的内存泄漏。

The code used (jsfiddle interferes with memory testing, so use your own server to test this):

使用的代码(jsfiddle 会干扰内存测试,所以使用你自己的服务器来测试):

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

If there is no memory leak, the used memory will increase by around 1000kb or less after the tests are run. However, if there is a memory leak, the memory will increase by about 16,000kb. Removing the event listener first always results in lower memory usage (no leaks).

如果没有内存泄漏,测试运行后使用的内存会增加大约 1000kb 或更少。但是,如果出现内存泄漏,内存会增加大约 16,000kb。首先删除事件侦听器总是会导致较低的内存使用量(无泄漏)。

Results:

结果:

  • IE6 - memory leak
  • IE7 - memory leak
  • IE8 - no memory leak
  • IE9 - memory leak (???)
  • IE10 - memory leak (???)
  • IE11 - no memory leak
  • Edge (20) - no memory leak
  • Chrome (50) - no memory leak
  • Firefox (46) - hard to say, doesn't leak badly, so maybe just inefficient garbage collector? Finishes with an extra 4MB for no apparent reason.
  • Opera (36) - no memory leak
  • Safari (9) - no memory leak
  • IE6 - 内存泄漏
  • IE7 - 内存泄漏
  • IE8 - 没有内存泄漏
  • IE9 - 内存泄漏 (???)
  • IE10 - 内存泄漏 (???)
  • IE11 - 没有内存泄漏
  • Edge (20) - 无内存泄漏
  • Chrome (50) - 没有内存泄漏
  • Firefox (46) - 很难说,不会泄漏很严重,所以也许只是低效的垃圾收集器?无缘无故以额外的 4MB 结束。
  • Opera (36) - 无内存泄漏
  • Safari (9) - 无内存泄漏

Conclusion: Bleeding edge applications can probably get away with not removing event listeners. But I'd still consider it good practice, in spite of the annoyance.

结论:出血边缘应用程序可能可以通过不删除事件侦听器而逃脱。但我仍然认为这是一个很好的做法,尽管有烦恼。