【问题标题】:Do we manually need to clean up unreferenced variables in a closure?我们是否需要手动清理闭包中未引用的变量?
【发布时间】:2015-12-28 01:32:36
【问题描述】:

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

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

    function inner() {
        return "Foo";
    }

    return inner;
}

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

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

这在今天是否成立?还是这篇文章过时了? (如果它已经过时,有人可以向我指出有关当前陷阱的资源)

【问题讨论】:

    标签: javascript memory-leaks


    【解决方案1】:

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

    因此,在返回内部函数之前设置data = null没有关系,因为内部函数不依赖(“关闭”)data

    如果内部函数确实依赖于data——也许它会返回它——那么设置data = null肯定不是你想要的,因为那时,好吧,它会是 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

    【讨论】:

    • 谢谢 torazaburo。让我担心的是,您提供的关于 Meteor 错误的文章中的最后一个 sn-p 确实在 V8 中泄漏了内存。
    • “实际上,我建议你主要不要担心内存泄漏。它们只发生在非常特殊的情况下”。我强烈不同意这种说法。至少分离的 DOM 在 SPA 中经常出现,每个开发人员都必须了解它。
    【解决方案2】:

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

    我记得上一次 JS 开发人员必须真正担心真正的泄漏是当 Internet Explorer(我认为是 6 和 7)对 DOM 和 JS 使用单独的内存管理时。正因为如此,可以将onclick 事件绑定到按钮,销毁按钮,但事件处理程序仍会滞留在内存中——永远(或直到浏览器崩溃或被用户关闭)。事后您无法触发处理程序或释放它。它只是坐在堆栈上,占用空间。因此,如果您有一个长期存在的 web 应用程序或创建和销毁大量 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 共享相同范围的代码也可以。

    所以在这种情况下,需要在资源使用和安全性之间进行权衡。

    【讨论】:

    猜你喜欢
    • 2023-03-11
    • 2011-08-21
    • 2019-11-30
    • 2021-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-08
    相关资源
    最近更新 更多