JavaScript是否进行垃圾回收?

5

有时候我会做一些类似以下的事情:

var img = new Image();
img.src = 'php/getpic.php?z=' + imid + '&th=0';
img.onload = function(){drawImages(img,contexts,sizes)};

解释

  1. 创建一个HTML图像元素。
  2. 分配其src属性。
  3. 分配其onload事件。
  4. 将一个或多个Canvas上下文传递给事件处理程序。
  5. 将加载的图像绘制到画布上。

我不清楚的是,JavaScript垃圾回收器是否会处理丢弃img元素的任务,还是我需要自己处理,否则就会面临缓慢的内存泄漏?


5
注意:交换步骤2和3。如果图像已被缓存,IE将在赋值onload = function() {}执行之前触发onload事件。 - DCoder
DrawImages 在这里并不太相关 - 它只是在 DOM 中绘制已加载到一个或多个画布(作为上下文传递在最后一行中)上的图像。完成后,img 对象就没有进一步的用处了。在此示例中,这些图像从未被缓存。 - DroidOS
2个回答

7

由于它在JavaScript和DOM之间创建了循环引用,因此在IE 6和7版本(以及非常旧的FF版本)中会发生内存泄漏。因为它会导致两个世界之间的循环引用,而IE 6和7使用单独的垃圾收集器,无法对具有这种循环引用的对象进行垃圾回收。

现代浏览器可以处理此问题而不会发生内存泄漏。

为了防止在IE 6和7中发生内存泄漏,在完成使用img时,请执行以下操作:

img.onload = null;

如果你只关心现代浏览器,你就不必担心它。(我很高兴IE 6和7的市场份额终于低到可以这样说!)

更新

你为onload分配的函数创建了一个闭包。该闭包包含对img的引用。只要该闭包存在于JScript的内存中(JScript是IE实现JavaScript的名称),img就无法从DOM的内存中进行垃圾回收。同样,只要img存在于DOM的内存中,闭包就无法从JScript的内存中进行垃圾回收,因为img.onload引用了你的函数。这是一个循环引用。换句话说,仅因为drawImages执行一次并不意味着它不会再次执行(JScript引擎不知道onload只会触发一次——那是DOM的领域),因此JScript必须保持闭包的生命周期。
你展示的模式是已知会在IE 6和7中创建内存泄漏的经典模式。它由(1)一个DOM节点、(2)该DOM节点上的事件处理程序创建的闭包和(3)闭包内对该DOM节点的引用组成。

我不太清楚的是 - 在我的情况下,我从来没有将新创建的img元素添加到DOM中。我使用它从服务器获取图像资源,然后使用加载的图像绘制到一个或多个画布上,此时我就完成了。对它唯一的引用是传递给drawImages的参数,我想象一旦该函数返回,该参数就变得可以进行垃圾回收。 - DroidOS
@DroidOS,我已经更新了我的答案,尝试更好地解释它。干杯。 - Nathan Wall
@DroidOS,我还想指出的另一件重要事情是,即使您没有将该元素添加到文档中,它仍然是一个DOM组件,并且该对象的垃圾回收是通过DOM的垃圾回收器完成的。 - Nathan Wall
谢谢你详细的解释,Nathan。我还没有来得及查看在 Chrome 堆调试器中发生了什么,但计划明天这样做。我猜我并不特别关心 IE,当然也不会关心史前版本,所以任何针对 IE 的问题都可以相对安全地忽略。 - DroidOS
在这种情况下,不用担心。在现代浏览器中,您不会出现内存泄漏问题。 - Nathan Wall

6
如果您担心内存泄漏,我建议您使用Google Chrome的堆调试器。在您的情况下,我进行了一个简单的测试,可以验证您的特定模式是否会导致内存泄漏。
<html>
    <head>
        <script>
            function test() {
                var img = new Image();
                img.src = 'php/getpic.php?z=1&th=0';
                img.onload = function(){ drawImages(img,contexts,sizes); };
            }
        </script>
    </head>
    <body>
        <input type="button" onclick="test()" value="test" />
    </body>
</html>

我建议在测试前先按一次“测试”按钮,以避免结果中出现不必要的噪声(第一次运行代码时会添加一些东西到内存中,这并不意味着存在内存泄漏)。
进行堆快照,按下测试按钮并进行堆快照。如果您想要实际结果,必须将视图从“摘要”切换到{{link1:“比较”}}。如果视图为空(在您的情况下,当我测试时),这意味着在该步骤中未添加任何内容,这意味着它没有内存泄漏。在另一种情况下,它将向您展示在快照之间添加或删除了什么。
注意:此方法无法帮助您解决旧浏览器的内存泄漏问题,但对于大多数现代浏览器中可能导致内存泄漏的问题是一个好的步骤。

谢谢您提供堆调试器教程 - 我会尝试并稍后回来。 - DroidOS
我最终使用堆调试器进行了大量测试。好消息是 - 没有内存泄漏! - DroidOS

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