我们手动需要清除闭包中的未引用变量吗?

时间:2015-09-30 09:56:38

标签: javascript memory-leaks

我正在阅读这篇关于内存泄漏的文章(http://javascript.info/tutorial/memory-leaks#memory-leak-size),其中提到这是一个内存泄漏:

function f() {
    var data = "Large piece of data";

    function inner() {
        return "Foo";
    }

    return inner;
}
  

JavaScript解释器不知道可能需要哪些变量   内在的功能,所以它保留了一切。在每个外部   LexicalEnvironment。我希望,新的口译员会尝试优化它,但是   不确定他们的成功。

文章建议我们在返回内部函数之前需要手动设置data = null

今天是否适用?或者这篇文章过时了吗? (如果它已经过时,有人可以指出我有关当前陷阱的资源)

2 个答案:

答案 0 :(得分:5)

现代引擎不会在外部范围内维护未使用的变量。

因此,如果在返回内部函数之前设置data = null并不重要,因为内部函数不依赖于(" close over")data

如果内部函数 依赖于data - 也许它会返回它 - 那么设置data = null肯定是你想要什么to,因为那样,它将为null而不是具有原始值!

假设内部函数确实依赖于data,那么是的,只要inner被指向(引用)某事,那么data的值将必须保持在身边。但是,那就是你想要的!如何在没有可用的情况下提供可用的东西?

请记住,在某些时候,保存f()返回值的变量本身将超出范围。此时,至少再次调用f()data将被垃圾收集。

一般规则是,您不必担心内存和JavaScript漏洞。这就是GC的重点。垃圾收集器可以很好地识别需要什么和不需要什么,并保持前者和垃圾收集后者。

您可能需要考虑以下示例:

function foo() {
  var x = 1;
  return function() { debugger; return 1; };
}

function bar() {
  var x = 1;
  return function() { debugger; return x; };
}

foo()();
bar()();

并检查其在Chrome devtools变量窗口中的执行情况。当调试器在foo的内部函数中停止时,请注意x不作为局部变量或闭包存在。出于所有实际目的,它不存在。

当调试器在bar的内部函数中停止时,我们会看到变量x,因为必须保留它才能返回。

  

今天是否适用?或者这篇文章过时了吗?

不,它没有,是的,确实如此。这篇文章已有四年历史,这是网络世界的一生。我无法知道jQuery是否仍会受到泄密,但如果是这样的话,我会感到惊讶,如果是这样的话,有一种简单的方法可以避免它们 - 不要使用它们jQuery的。文章的作者提到的与DOM循环相关的漏洞和现代浏览器中不存在事件处理程序,我的意思是IE10(更可能是IE9)及更高版本。如果你真的想了解内存泄漏,我建议你找一个更新的参考资料。实际上,我建议你主要不要担心内存泄漏。它们仅在非常特殊的情况下发生。由于这个确切的原因,现在很难在网上找到很多主题。这是我发现的一篇文章:http://point.davidglasser.net/2013/06/27/surprising-javascript-memory-leak.html

答案 1 :(得分:1)

除了@ torazaburo的优秀答案之外,值得指出的是,该教程中的示例并非泄漏。泄漏当程序删除对某事物的引用但不释放它消耗的内存时会发生什么。

我最后一次记得JS开发人员真的担心真正的漏洞是因为Internet Explorer(我认为6和7)对DOM和JS使用了单独的内存管理。因此,可以将onclick事件绑定到按钮,销毁按钮,并且仍然将事件处理程序搁置在内存中 - 永远(或直到浏览器崩溃或被用户关闭)。你无法触发处理程序或在事后发布它。它只是坐在堆栈上,占据了空间。因此,如果您有一个长期存在的webapp或创建并销毁了大量DOM元素的网页,那么您必须非常勤奋,以便在销毁之前始终取消绑定事件。

我在iOS中也遇到了一些令人讨厌的漏洞,但这些都是漏洞,并且(最终)被Apple修补了。

也就是说,一个优秀的开发人员在编写代码时需要牢记资源管理。考虑这两个构造函数:

function F() {
    var data = "One megabyte of data";

    this.inner = new function () {
        return data;
    }
}

var G = function () {};
G.prototype.data = "One megabyte of data";
G.prototype.inner = function () {
    return this.data;
};

如果你要创建一千个F个实例,浏览器必须为这个巨大字符串的所有副本分配额外的千兆字节内存。每当你删除一个实例时,当GC最终恢复该ram时,你可能会获得一些屏幕上的混乱。另一方面,如果你创建了G的一千个实例,那么巨大的字符串将被创建一次并被每个实例重用。这是一个巨大的性能提升。

F的优点是巨大的字符串本质上是私有的。构造函数之外的其他代码都不能直接访问该字符串。因此,F的每个实例都可以根据需要改变该字符串,并且您永远不必担心会导致其他实例出现问题。

另一方面,G中的巨大字符串可供任何人更改。其他实例可以更改它,并且任何与G共享相同范围的代码也可以。

因此,在这种情况下,在资源使用和安全性之间存在权衡。