事件处理中的内存泄漏

3

最近我一直在阅读关于内存泄漏的文章,但我还没有完全理解其中的所有内容,对于我自己的编程风格也有一些疑问。具体来说,我不确定我处理事件的方式是否可能导致内存泄漏。请考虑以下代码:

function Wrapper(text) {
    this.text = text;
    this.bindHandlers();
};

Wrapper.prototype.onClick = function (e) {
    alert(this.text);
};

Wrapper.prototype.bindHandlers = function () {
    var t = this, div = $('<div>' + this.text + '</div>');

    var reallyHugeArray = [1,2,3...]; // an array of 100000 elements for example

    div.click(function (e) {
        // all variables of the parent function are in scope for this function, including reallyHugeArray
        t.onClick(e);
    });

    $(document).append(div);
};

var a = new Wrapper('testString');

// had enough fun with the Wrapper, now let's nullify it
a = null;

正如您所看到的,我喜欢使用匿名函数作为事件处理程序,以便更方便地访问实例特定变量(在这种情况下,在onClick函数中的this.text)和函数。然而,如果我理解正确,将匿名函数放在一个函数内(作为事件处理程序),该函数可以访问本地范围,会禁用垃圾回收器删除本地变量,从而创建泄漏。
因此,我的问题是,这种事件处理方法是否会创建内存泄漏,如果会,是否有任何方法可以防止它,但仍然可以方便地访问实例变量和函数?
(离题:函数内部的函数内部的函数使JavaScript听起来像梦境)

任何JS和宿主对象之间的循环引用都可能在IE6/7中导致内存泄漏。但这不会在_任何一个好的浏览器_中导致内存泄漏。 - Raynos
你的意思是传递给 div.click 的函数不能被垃圾回收器删除,对吗? - Gumbo
一个名为 bindHandlers 的函数,它创建 DOM 节点并将它们附加到文档中,这是不好的命名惯例。 - Raynos
@Gumbo 我的意思是匿名点击处理程序可以访问变量t(整个Wrapper对象),但也可能访问非常大的数组等(我会稍微修改代码)。现在,如果出于某种原因我不再需要Wrapper并将其定义为null等,那么这些可能很大的变量是否能够正确地从内存中释放?Raynos:这只是我随便编的一个示例,以更好地解释问题,不是项目中真实的代码,但感谢你的建议。 - zatatatata
@Raynos,这里的循环引用在哪里? - davin
@davin 没有任何一个,因为jQuery避免使用它们并且不直接访问DOM。我的意思更多是你应该关心的唯一内存泄漏。 - Raynos
1个回答

1
在你的特定示例中,匿名点击处理程序为其上方的范围创建了一个函数闭包。这意味着tdivreallyHugeArray的值将在匿名点击处理程序函数的生命周期内保持不变。
这不是真正的内存“泄漏”,而是内存“使用”。它不会随着时间的推移而变得越来越糟,它只是使用那些本地变量tdivreallyHugeArray占用的固定内存量。这通常是JavaScript编程的优点,因为这些变量对内部函数可用。但是,正如你所想的那样,如果你希望释放该内存,它偶尔可能会引起问题。
在引用其他东西(DOM对象或其他JS变量)的情况下,由于这些外部变量继续存在,它们所引用的所有内容也继续存在,并且无法被垃圾收集器释放。一般来说,这不是一个大问题。往往会导致问题的是在网页使用过程中反复执行的事情,或者在具有大量迭代的大型循环中执行的事情。像这样仅执行一次的东西只会多使用一点内存,从那时起,构造的内存使用量就是恒定的。

如果由于某种原因,您一遍又一遍地绑定此事件处理程序,每次都创建一个新的函数闭包并永远不释放它们,那么可能会出现问题。

我发现在JavaScript中使用这种结构非常有用。 我认为它不是要避免的东西,但值得理解,以防您引用了真正大的东西,您希望释放它们,短暂的东西应该被释放,因为您不需要长期使用它们或者您正在反复执行某些操作。 在这种情况下,您可以显式将本地变量设置为null,如果您不需要在内部函数中使用它们,则杀死它们的引用并允许垃圾收集器执行其任务。 但是,这通常不是您需要做的事情-只是在某些情况下需要注意的事情。


感谢您的出色回答,但是当将实例设置为“null”时,最后一行会发生什么。由于有一个绑定到匿名函数的DOM元素,该函数引用Wrapper实例,我认为对象会保留在内存中,因为我们对其有引用。然后,在以后的某个时间,如果从DOM中删除了绑定的div,而没有删除事件处理程序,那么变量(t,reallyHugeArray)是否会被收集或仍将保留在内存中?(我知道jQuery进行抽象以防止内存泄漏,但假设我没有使用.remove()) - zatatatata
抱歉,忘记了@jfriend00。 - zatatatata
1
@Zanfa 原帖作者会自动收到回复通知,这种情况下不需要使用 @ 符号 :) - Jonas Høgh
当你设置a = null;时,你部分释放了对DOM对象的JS引用。如果对象在DOM中,则不会被删除,因为它在DOM中。如果对象不在DOM中,则如果没有其他引用,它可能有资格进行垃圾回收。灰色地带出现在您与DOM对象关联的事件处理程序。我不知道jQuery在这方面的工作原理,但我已经读到,在一些旧浏览器中,一个具有事件处理程序附加到它的对象不会被垃圾回收。 - jfriend00
在您上述的特定情况中,对象位于DOM中,事件处理程序仍然存在,因此包含本地变量和匿名函数的函数闭包应保持活动状态,不应被删除。 - jfriend00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接