(注意:我在下面使用jQuery,但问题实际上是一般的Javascript问题。)
说我有一个div#formsection
,其内容使用AJAX重复更新,如下所示:
var formSection = $('div#formsection');
var newContents = $.get(/* URL for next section */);
formSection.html(newContents);
每当我更新这个div时,我trigger a custom event,它将事件处理程序绑定到一些新添加的元素,如下所示:
// 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 */});
所以:我将事件处理程序绑定到某些DOM节点,然后删除这些DOM节点并插入新节点(html()
这样做)并将事件处理程序绑定到新的DOM节点。
我的事件处理程序是否与它们绑定的DOM节点一起被删除了?换句话说,当我加载新的部分时,很多无用的事件处理程序堆积在浏览器内存中,等待对于不再存在的DOM节点上的事件,还是在删除DOM节点时将其清除?
奖金问题:如何自行测试?
答案 0 :(得分:21)
事件处理程序函数与其他变量的垃圾收集相同。这意味着当解释器确定没有可能获得对函数的引用的方法时,它们将从内存中删除。但是,简单地删除节点并不能保证垃圾收集。例如,获取此节点和关联的事件处理程序
var node = document.getElementById('test');
node.onclick = function() { alert('hai') };
现在让我们从DOM中删除节点
node.parentNode.removeChild(node);
所以node
将不再在您的网站上显示,但它显然仍然存在于内存中,事件处理程序也是如此
node.onclick(); //alerts hai
只要仍可以某种方式访问node
的引用,它的相关属性(其中onclick
就是一个)将保持不变。
现在让我们尝试一下,不要创建悬空变量
document.getElementById('test').onclick = function() { alert('hai'); }
document.getElementById('test').parentNode.removeChild(document.getElementById('test'));
在这种情况下,似乎没有其他方法可以访问DOM节点#test,因此在运行垃圾收集周期时,应该从内存中删除onclick
处理程序。
但这是一个非常简单的案例。 Javascript使用闭包可能会极大地复杂化垃圾收集性的确定。让我们尝试将稍微复杂的事件处理函数绑定到onclick
document.getElementById('test').onclick = function() {
var i = 0;
setInterval(function() {
console.log(i++);
}, 1000);
this.parentNode.removeChild(this);
};
因此,当您单击#test时,该元素将立即被删除,但是一秒钟后,每隔一秒,您将看到一个增加的数字打印到您的控制台。该节点被删除,并且不可能进一步引用它,但似乎它的一部分仍然存在。在这种情况下,事件处理函数本身可能不会保留在内存中,但它创建的范围是。
所以答案我猜是;这取决于。如果存在对已删除DOM节点的悬空可访问引用,则它们的关联事件处理程序仍将与其余属性一起驻留在内存中。即使不是这种情况,事件处理程序函数创建的作用域可能仍在使用中并且在内存中。
在大多数情况下(并且高兴地忽略IE6)最好只相信垃圾收集器来完成它的工作,毕竟Javascript不是C.但是,在最后一个示例的情况下,编写某种类型的析构函数来隐式关闭功能非常重要。
答案 1 :(得分:6)
jQuery在从DOM中删除元素时竭尽全力避免内存泄漏。只要您使用jQuery删除DOM节点,就应该通过jQuery处理事件处理程序和额外数据的删除。我强烈建议阅读John Resig的Secrets of a JavaScript Ninja,因为他详细介绍了不同浏览器中的潜在泄漏以及jQuery等JavaScript库如何解决这些问题。如果您不使用jQuery,则在删除DOM节点时,您必须担心通过孤立事件处理程序泄漏内存。
答案 2 :(得分:2)
您可能需要删除这些事件处理程序。
Javascript memory leaks after unloading a web page
在我们的代码中,它不是基于jQuery,而是一些原型异常,我们在类中有初始化器和析构函数。我们发现,当我们不仅在运行期间销毁我们的应用程序而且还销毁单个小部件时,从DOM对象中删除事件处理程序是绝对必要的。
否则我们最终会在IE中出现内存泄漏。
在IE中出现内存泄漏非常容易 - 即使我们卸载页面,我们也必须确保应用程序“干净”关闭,整理掉所有内容 - 或者IE进程会随着时间的推移而增长。
修改:要正确执行此操作,我们会在window
上为unload
事件设置一个事件观察者。当那个事件发生时,我们会调用我们的析构函数链来正确清理每个对象。
一些示例代码:
/**
* @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);
},
答案 3 :(得分:2)
documentation on jQuery's empty()
method都回答了我的问题,并为我解决了问题。它说:
为了避免内存泄漏,jQuery会删除 其他结构,如数据和 来自子元素的事件处理程序 在删除元素之前 自己。
所以:1)如果我们没有明确地执行此操作,我们会发生内存泄漏,2)使用empty()
,我可以避免这种情况。
因此,我应该这样做:
formSection.empty();
formSection.html(newContents);
我仍然不清楚.html()
是否会自行解决这个问题,但是要确保一条额外的行不会打扰我。
答案 4 :(得分:1)
我想知道自己,经过一点点测试后,我认为答案是肯定的。
当您从DOM中删除.remove()时,会调用removeEvent。
如果您想自己查看,可以尝试这个并通过设置断点来遵循代码。 (我使用的是jquery 1.8.1)
首先添加一个新的div:
$('body').append('<div id="test"></div>')
检查$.cache
以确保没有附加任何事件。 (它应该是最后一个对象)
附加点击事件:
$('#test').on('click',function(e) {console.log("clicked")});
测试它并在$.cache
中查看新对象:
$('#test').click()
删除它,你可以看到$.cache
中的对象也消失了:
$('#test').remove()