JavaScript 闭包中的垃圾回收

8

我需要一些帮助来理解这个如何工作(或者说它是否真的有效)。

  1. 在网页中,我创建了一个点击事件监听器。
  2. 在监听器内部,我创建了一些随机类的实例,并将节点设置为其属性。因此,如果 var classInstance 是该实例,则可以像 classInstance.rootNode 这样访问该节点。
  3. 当监听器触发时,我设置一个 ajax 请求,将 classInstance 保留在闭包中,并将 ajax 响应传递给 classInstance 并使用它来修改 rootNode 的样式、内容或其他内容。

我的问题是,一旦我完成了 classInstance,假设没有任何其他引用它,并且仅靠它本身,在其自己的闭包中什么也不保留,那么垃圾回收器会处理它吗?如果不行,我应该如何标记它以便进行处理?


2
展示该事件监听器的代码。但是听起来应该在ajax返回后收集,因为此后没有任何执行代码引用它了。 - Bergi
@Bergi:Nips,这就是我想说的...我打了一半的《达芬奇密码》,而你在一个评论中概括了它的一半... :) - Elias Van Ootegem
@Bergi - 啊,这段代码是基于javelin的,如果我在这里发布样例来解释会太冗长。但我明白你的意思了。谢谢。 - Jibi Abraham
2个回答

8

作为回应 @Beetroot-Beetroot 的疑问(我也有同样的疑问),我进行了更深入的挖掘。我建立了 this fiddle,并使用了 chrome dev-tools' timelinethis article 作为指导。在这个fiddle中,两个几乎相同的处理程序创建了一个包含2个日期对象的闭包。第一个只引用 a,第二个引用了 ab 两者。尽管在两种情况下只有 a 能够真正被暴露出来(硬编码值),但第一个闭包使用的内存要少得多。无论是由于 JIC(即时编译)还是 V8 的 JS 优化魔法,我都不能确定。但从我所读到的内容来看,我会说是 V8 的 GC 在 tst 函数返回时释放了 b,而在第二种情况下它无法释放(当 tst2 返回时,bar 引用了 b)。我感觉这并不是那么离奇,我也不会惊讶地发现 FF 甚至 IE 也会以类似的方式工作。
我刚添加了这个可能不相关的更新,为了完整起见,也因为我觉得链接到谷歌的 dev-tools 文档是一种增值。

这有点取决于情况,简单来说:只要您无法再引用classInstance变量,它就应该被垃圾回收,而不管它自己是否存在循环引用。我已经测试了很多类似于您在此描述的构造。也许 值得一看
我发现闭包和内存泄漏并不常见或容易发生(至少不再是这样)。

但正如被接受的答案所说:几乎不可能知道什么时候代码会泄漏。
再次阅读您的问题,我想说:不,您不会泄漏内存: classInstance变量没有在全局范围内创建,而是被传递给各种函数(因此各种范围)。每次函数返回时,这些作用域都会消失。如果已将classInstance传递到另一个函数/作用域中,则不会对其进行GC处理。但是,只要最后一个引用classInstance的函数返回,对象就会被标记为GC。当然,它可能是循环引用,但是这是一个无法从任何地方访问的引用,除了它自己的作用域。
您实际上不能称之为闭包:闭包发生在某种形式的外部范围暴露的情况下,这在您的示例中并未发生。

我很糟糕地解释这种东西,但是简单概括一下:

var foo = (function()
{
    var a, b, c, d;
    return function()
    {
        return a;
    }
})();

GC将释放内存bcd的引用:它们已经超出了作用域,无法访问它们...
var foo = (function()
{
    var a, b, c, d;
    return function()
    {
        a.getB = function()
        {
            return b;
        }
        a.getSelf = function()
        {
            return a;//or return this;
        }
        return a;
    }
})();
//some code
foo = new Date();//

在这种情况下,由于明显的原因,b也不会被GC回收。 foo暴露了ab,其中a是包含循环引用的对象。但是,一旦foo = new Date()foo就失去了对a的任何引用。当然,a仍然引用自身,但是a不再被暴露:它可以引用任何它想要的东西。大多数浏览器都不会关心,会GC回收ab。事实上,我已经检查过Chrome、FF和IE8,它们都完美地GC了上面的代码...没问题。

GC将释放内存b、c和d的引用:我曾经认为这是GC的行为方式,但后来明白了无论内部是否使用,外部环境中的每个成员都会被保留。我一直在寻找关于JavaScript中此GC行为的权威参考资料。你知道有吗? - Beetroot-Beetroot
@Beetroot-Beetroot:我认为没有任何权威的参考资料可用,因为GC的工作方式取决于各种引擎。但由于V8是开源的,你可以进行一些挖掘。我只是基于GC属于标记清除类型的假设。假设它标记了d,因为它不再被引用,因此可以删除。当然,在幕后JS使用调用对象,因此最终可能只有整个范围被释放...所以说实话:我认为没有人确切知道。 - Elias Van Ootegem
@Beetroot-Beetroot:我进行了一些检查,使用这个fiddlechrome dev-tools' timeline这篇文章,我现在相当确信V8会尽快收集每个单独的引用。在两个闭包中,都创建了2个日期对象,但第一个闭包占用的内存要比第二个少得多。原因可能是JIC或V8优化,无论哪种方式,我的最初的“理论”仍然成立。 - Elias Van Ootegem
@Beetroot-Beetroot:好的,回到我们起点吧:我猜这取决于不同的实现方式。我想更多地了解这个东西。我真的觉得很有趣。既然你费心测试了这个,我认为你也对此感兴趣。所以我在这里添加一个链接到我的问题,也许你可以在那里阐明一些问题,或者发现一些新的东西(谁知道呢...:-P)。 - Elias Van Ootegem
1
Elias - 是的,我非常感兴趣。也许我们可以一起开发一个“Van-Ootegem-Beetroot”基准测试!现在没有时间,但我稍后会再回来这里。 - Beetroot-Beetroot
显示剩余3条评论

2

我不是这个问题的专家,但我非常确定垃圾回收器不会清除它。实际上,可能永远不会清除,因为您在事件监听器和DOM节点之间创建了循环引用。要允许其被垃圾回收,您应该将这些引用(事件监听器和/或rootNode)中的一个或两个设置为未定义或null。

不过,如果您正在创建许多这些classInstances,或者它可以在页面的生命周期内多次创建,则应该关注此问题。否则,这是一种不必要的优化。


1
你听起来像是IE的垃圾收集器 :-) 当然,即使它们包含主机元素,循环引用也会被收集。 - Bergi

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