【问题标题】:Memory Leak In Javascript from shared closures来自共享闭包的 Javascript 内存泄漏
【发布时间】:2019-08-01 18:16:35
【问题描述】:

我是 javascript 新手,我发现了一篇关于内存的文章和一个具体示例。article

在例子中说:

重要的是,一旦为闭包创建了一个作用域, 在同一个父范围内,该范围是共享的。在这种情况下, 为闭包 someMethod 创建的作用域由未使用共享

我将它更改为固定版本,但同样的情况发生了。 someMethod 不在同一个父范围内,但内存泄漏仍然存在。我错过了什么?

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing)
      console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log(someMessage);
    }
  };
};
setInterval(replaceThing, 1000);

固定版本:

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing)
      console.log("hi");
  };
  (function(){
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log(someMessage);
    }
  };
})()
};
setInterval(replaceThing, 1000);

【问题讨论】:

  • "someMethod 不在同一个父范围内" - 你的意思是什么?
  • @Bergi Unsed 方法将 replaceThing 作为父范围,而 someMethod 将匿名函数作为父范围

标签: javascript memory-leaks


【解决方案1】:

这两个闭包是否在完全相同的范围内并不重要。通过该 IIFE 引入一个中间范围(顺便说一句,应该优化掉它,因为它不包含任何变量),并不会改变 theThing.someMethod 仍然关闭 originalThing 变量的事实。

修复此特定问题需要在不再需要 originalThing 时手动清除它,或者在保留的函数无权访问的范围内声明它:

var theThing = null;
function replaceThing() {
  {
    const originalThing = theThing;
    function unused() {
      if (originalThing)
        console.log("hi");
    }
  }
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod() {
      console.log(someMessage);
    }
  };
};
setInterval(replaceThing, 1000);

// or, ES5 style:
var theThing = null;
var replaceThing = function () {
  (function(){
    var originalThing = theThing;
    var unused = function () {
      if (originalThing)
        console.log("hi");
    };
  })();
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log(someMessage);
    }
  };
};
setInterval(replaceThing, 1000);

虽然可以通过聪明的编译器优化特定示例,但您需要了解需要共享词法环境的原因。考虑代码

 var log, increment, decrement;
 {
   let heavy = [];
   let count = 0;
   log => {
     console.log(heavy, count);
   };
   increment = () => {
     heavy.push("long string");
     count++;
   };
   decrement = () => {
     count--;
   };
 }
 increment();
 log();
 increment = log = null; // release memory, `heavy` is no more used anywhere?
 decrement();

因为log 和increment 都引用count 和heavy,所以这两个变量放在同一个词法环境中。 (为可以独立进行垃圾收集的单独变量创建单独的环境需要从范围链(简单链表)切换到更复杂的东西(有向无环图)。

但decrement 也引用了count 变量,因此它关闭了同一个环境对象。 decrement 不使用 heavy 没关系。现在,当所有引用 heavy 变量的函数都被垃圾回收时,decrement 仍然引用了 heavy 值。

【讨论】:

  • 在文章中明确指出“一旦为同一父作用域中的闭包创建了作用域,则该作用域是共享的”。它们不在同一个父范围内,但仍然存在泄漏
  • @TORO 好吧,准确地说,它应该是“一旦因为闭包而创建了一个作用域,该作用域中的所有函数都会共享它”。 Meteor 博客上的 original article 解释得更好,包括词汇环境的链接链。
  • 非常感谢
猜你喜欢
  • 1970-01-01
  • 2021-08-02
  • 1970-01-01
  • 2013-01-11
  • 2012-06-26
  • 2013-03-29
  • 2013-07-12
  • 2016-01-12
  • 2015-06-11
相关资源
最近更新 更多